Framework and Python recipes
Send AI SDK, LangChain and explicitly instrumented Python model and tool spans through OTLP.
On this page
Choose the integration for your actual client, operation and runtime. A recognized dependency is inventory; a correctly attributed, stored model/tool trace is runtime evidence. The shared compatibility catalog drives CLI guidance, project setup and verification.
Expanded integrations are available in this checkout and its generated downloads. Publication and production deployment remain pending. Local fixtures do not qualify older deployed receivers or live model vendors.
Support matrix
| Route | Exact local qualification | Capture boundary |
|---|---|---|
| Native SDK | Node 22.22.0, Windows; provider versions in the method matrix | Explicit client wrapping, measured models/tools and separately labelled inferred tool steps |
| AI SDK | ai 7.0.107, @ai-sdk/openai 4.0.71, @ai-sdk/otel 1.0.107; Node 22.22.0 | Framework model/tool spans; outer wrapper usage is counted on model children only |
| LangChain | Core 1.2.11, OpenAI 1.5.13, OpenInference 4.1.1; Node 22.22.0 | Awaited callback instrumentation for the tested model/tool paths |
| Python | tracehatch-python 0.1.0, openai 3.16.2, OTel 1.44.0; Windows CPython 3.11.14 and 3.13.0 | Explicit sync/async Chat Completions, streams, trace/tool contexts |
| .NET | OpenAI 2.14.0, OTel 1.19.0; Windows .NET 10.0.12 / SDK 10.0.401 | Explicit Chat Completions, async streams, task context and tools |
| Go | openai-go/v3 3.63.1, OTel 1.46.0; Windows Go 1.27.1 | Explicit Chat Completions, streams, goroutine context and tools |
| Java | OpenAI Java 4.65.0, OTel 1.66.0; Windows Microsoft OpenJDK 25.0.4.1 / Maven 3.9.12 | Explicit Chat Completions, streams, executor context and tools |
| Bun / Deno | Bun 1.4.2 and Deno 2.9.6, Windows; OpenAI 7.15.0 | Shared native implementation through Node compatibility APIs; local only |
| Workers | workerd 1.20260730.1 / Miniflare 4.20260730.0, compatibility date 2026-07-30 and nodejs_compat; OpenAI 7.15.0 | Local emulator; no cloud deployment or generic Edge claim |
Node OTLP recipes pin OTel API 1.9.1, SDK/resources 2.11.0 and HTTP exporter 0.222.0. Other versions, operating systems, operations and deployment hosts need their own evidence. Browser/mobile bundles must never contain server ingest keys. See what is recorded for the native provider-method matrix.
Choose the destination
Select your integration and environment-scoped ingest key in Project setup. OTLP uses TRACEHATCH_API_KEY and the complete OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, such as https://api.tracehatch.com/api/v1/otlp/v1/traces. Local/self-hosted installs use their API origin, not the dashboard origin. HTTP JSON and protobuf traces are supported; gRPC, metrics and logs are not. Personal/service access tokens are not ingest keys.
Downloads contain source and pinned dependency manifests without keys or installed dependencies. Fixture mode uses local model responses, without a paid provider key. Exporting those examples to your project still needs an ingest key. An example proves the recipe works; capture your own application's action separately.
Owned native clients
The native helpers on this page are source-qualified additions awaiting publication. Download the SDK archive from this dashboard, then install that file in the server package that makes model calls:
npm install ./tracehatch-sdk-0.15.0.tgzUse the dashboard built from this checkout and its matching receiver. A previously published package with the same development version does not establish that these new entry points are present. The archive bundles the native SDK; optional framework helpers still require the exact peers listed below.
Use createClient when requests or tenants need independent destinations, identities or policy. Convenience wrap/trace calls use a default client and do not reconfigure explicit clients.
import { createClient } from "@tracehatch/sdk"
import OpenAI from "openai"
const telemetry = createClient({
apiKey: process.env.TRACEHATCH_API_KEY,
service: { name: "support-api" },
captureBodies: false,
})
const model = telemetry.wrap(new OpenAI())
try {
await telemetry.trace("support request", async () => {
await model.chat.completions.create({
model: "gpt-4.1-mini",
messages: [{ role: "user", content: "Help with my order" }],
})
})
} finally {
await telemetry.shutdown()
}A provider object has one capture owner; conflicting ownership fails visibly. Keep provider settings and retry behavior unchanged. Flush/shutdown at the actual application lifetime boundary; process-exit hooks cannot recover telemetry after a host freezes or kills the process.
Native OpenRouter
@openrouter/sdk 1.3.6 is locally qualified for chat.send, responses.send, beta.responses.send and embeddings.generate:
import { wrap, flush } from "@tracehatch/sdk"
import { OpenRouter } from "@openrouter/sdk"
const client = wrap(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }))
await client.chat.send({
chatRequest: {
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }],
},
})
await flush()The parameter containers are chatRequest, responsesRequest and requestBody. An openai client pointed at OpenRouter retains OpenAI shapes. @openrouter/agent is a separate framework: direct wrapping does not qualify hidden framework calls. No live OpenRouter account, every routed model or out-of-band Broadcast connector has been qualified.
Vercel AI SDK
Download the framework example:
tar -xzf frameworks-node.tar.gz
cd frameworks-node
npm ci --ignore-scripts
npm run ai-sdk
# After setting the project key and OTLP traces endpoint:
node run.mjs ai-sdk --scenario success --exportThe standalone recipe uses AI SDK 7's telemetry.integrations with the exact @ai-sdk/otel 1.0.107 scope. Older experimental_telemetry.tracer snippets do not activate this instrumentor. Pass telemetry to each model entry point and flush after consuming streams. The receiver suppresses aggregate invoke_agent usage; the two-model/one-tool success fixture totals 50 input / 18 output tokens once.
An optional source-distributed helper shares your existing provider:
import { createAiSdkTelemetry } from "@tracehatch/sdk/ai-sdk"
const telemetry = createAiSdkTelemetry({ provider, captureBodies: false })
// Pass telemetry to generateText() / streamText().It keeps the qualified scope and suppresses overlapping native capture at the model boundary. It never registers a provider. Install the exact framework/OTel peers above before using this optional subpath; the base SDK does not import them. The standalone download works without this helper.
LangChain
Use the same framework download and locked installation:
npm run langchain
node run.mjs langchain --scenario success --exportThe recipe instruments OpenInference's callback manager once, awaits handlers and passes it to each model/tool invocation. Constructor-only callbacks can duplicate handlers. It supplies the provider only for the known ChatOpenAI path where OpenInference 4.1.1 omits it. Application roots own session/user identity; the success fixture totals 50/18 tokens.
import {
createLangChainCallbacks,
withLangChainModelCapture,
} from "@tracehatch/sdk/langchain"
const callbacks = await createLangChainCallbacks({
provider,
captureBodies: false,
})
const model = withLangChainModelCapture(existingChatModel)
await model.invoke(messages, { callbacks })Reuse callbacks for tool invocations. These optional helpers refuse conflicting instrumentation ownership; they do not replace an existing provider/policy. Apply the model ownership helper only to models, never an entire agent, graph or tool. Arbitrary retrievers/runnables and LangGraph/CrewAI/LlamaIndex do not gain blanket support.
Python OpenTelemetry
The Python download includes installable tracehatch-python 0.1.0 source. It requires neither an unpublished PyPI download nor Node:
tar -xzf python-otlp.tar.gz
cd python-otlp
python -m venv .venvActivate .venv\Scripts\Activate.ps1 in PowerShell or source .venv/bin/activate on POSIX, then:
python -m pip install -r requirements.txt
python main.py --scenario success --record recorded
# With the project key and full OTLP traces endpoint:
python main.py --scenario successconfigure owns a non-global OTel provider. Tracehatch(existing_tracer_provider) installs no processor and leaves shutdown to the application. Explicit .chat/.achat methods capture actual OpenAI/AsyncOpenAI Chat Completions; trace/tool contexts describe your application work. Do not combine these methods with automatic instrumentation of the same call; cross-instrumentor suppression is not claimed.
from openai import OpenAI
from tracehatch import configure
telemetry = configure(service_name="support", capture_bodies=False)
try:
with OpenAI() as client:
with telemetry.trace("support request", user_id="opaque-user-id"):
result = telemetry.chat(
client,
model="gpt-4.1-mini",
messages=[{"role": "user", "content": "Help with my order"}],
)
finally:
telemetry.flush()
telemetry.shutdown()Use await telemetry.achat(...) for AsyncOpenAI. Consume streams inside with stream / async with stream and close abandoned streams. Results and chunks stay the provider's original objects; the stream wrapper is not an instance of its original stream class. Interrupted output is partial and absent usage stays unknown. Included request/background examples cover async context, copy_context for thread pools and explicit W3C extraction.
Bodies default off, including exception messages/stacks. --capture-bodies opts into bounded local masking; --metadata-only keeps the default. This matcher is narrower than Node's and does not fetch remote project policy. Supply local policy before enabling bodies. Wheel/sdist installs passed on the two Windows Python versions above; this is not Anthropic, Python-framework or other-operation parity.
Dotnet OpenTelemetry
Download the .NET example, extract it and enter dotnet-otlp:
dotnet restore --locked-mode
dotnet run -- --fixture --record recorded
# With the project key and complete OTLP traces endpoint:
dotnet run -- --fixture --export --scenario successExplicit Activity spans surround ChatClient.CompleteChatAsync, CompleteChatStreamingAsync and tool execution. Task context and early async-stream disposal are tested. Add the source/exporter to the existing provider when present. ASP.NET hosting remains an unqualified deployment. Scope: tracehatch-dotnet-example 1.0.0.
Go OpenTelemetry
Download the Go example, extract it and enter go-otlp:
go mod download
go run . --fixture --record recorded
go run . --fixture --export --scenario successChat.Completions.New and NewStreaming use explicit context.Context parentage. Pass context into goroutines and request/worker boundaries. Cancellation/concurrent goroutines are tested; hosted net/http/broker deployment and a race-detector run are not. Scope: tracehatch-go-example 1.0.0.
Java OpenTelemetry
Download the Java example, extract it and enter java-otlp:
mvn -q compile exec:exec '-Dexample.args=--fixture --record recorded'
mvn -q compile exec:exec '-Dexample.args=--fixture --export --scenario success'The real client uses chat().completions().create and createStreaming. Context.wrap preserves executor ownership; close scopes/streams and end spans in finally. Executor qualification does not cover servlet, reactive or Kotlin-coroutine deployments. Scope: tracehatch-java-example 1.0.0.
All three language recipes are metadata-only: no model/tool bodies, credentials, exception messages or stacks are exported. Each tests success, streaming, model/tool errors, cancellation, concurrency and exporter rejection. Successful fixtures have two models and one tool with 42/16 tokens. --fixture --export sends a marked example to your project while the provider stays local. Remove --fixture only for a deliberate billable call with OPENAI_API_KEY; live vendors remain unqualified. These recipes do not fetch remote capture policy or install new Tracehatch language packages.
HTTP transport
createFetchAdapter from @tracehatch/sdk/transport accepts an owned capture client, the application's existing fetch, an exact baseUrl and a declared openai or anthropic protocol. Inject it into that client's transport option. It never patches global fetch or infers support from a hostname; unrelated routes pass through unchanged and custom hosts retain their provider identity.
It observes bounded JSON/SSE bytes as the application consumes them. Under native provider capture, HTTP attempts do not duplicate logical model usage; standalone transport owns its own generation. Opaque/signed requests, arbitrary transports and other protocols remain outside this route.
OpenAI background jobs
The optional @tracehatch/sdk/openai-operations source entry exports wrapOpenAIJobs(telemetry, openAIClient) for OpenAI 7.15.0 background Responses create/retrieve/cancel. One generation remains open through polling and counts terminal usage once. Keep the wrapper alive: it holds at most 128 active jobs for 120 seconds. Expired/unknown jobs are forwarded without invented capture. Raw asResponse() is incomplete; background streaming and cross-process resumption are unqualified.
OpenAI media and batch
The same entry exports wrapOpenAIMedia and wrapOpenAIBatches, each taking an owned telemetry client and the actual OpenAI 7.15.0 client. Fixtures cover nonstreaming images.generate/edit, audio.transcriptions.create, binary audio.speech.create, and batch create/retrieve/cancel.
These are metadata-only custom operations. Helpers never retain prompts, raw media, transcripts or URLs, even when general body capture is enabled. Binary responses are observed only as read, cloned or cancelled by the caller. Known batch output files are parsed as consumed JSONL, bounded to 512 rows and 64 KiB per line. Request counts and result usage are distinct; media units do not become chat tokens or text-model prices. No video operation or durable media storage/playback is qualified.
OpenAI realtime
observeOpenAIRealtime(telemetry, existingRealtimeWS) observes OpenAI 7.15.0 OpenAIRealtimeWS and returns a detach function. Local TLS WebSocket fixtures with ws 8.21.3 test metadata, terminal status/usage, premature closure and application event/error ordering. Detaching does not close the socket. Bounds: one-hour sessions, 120-second responses and 128 active responses. These custom operations are unpriced; WebRTC, browser/mobile authorization and deployed realtime services are unqualified.
Custom OpenTelemetry
Reuse an existing tracer provider and HTTP exporter for custom operations. A custom span proves transport and supplied attributes, not automatic model/tool capture. The optional createOtelProcessor({ apiKey, endpoint, captureBodies, redact }) from @tracehatch/sdk/otel adds a standard processor to the existing provider configuration. It provides bounded local redaction and metadata-only defaults, without replacing a provider or fetching native remote policy.
Distributed context and runtime lifetime
Native @tracehatch/sdk/propagation exposes injectTraceContext, extractTraceContext and traceContext. Pass extracted context explicitly as TraceOptions.parent; native propagation sends only traceparent. Python uses extract_context/inject_context. Neither imports user/session identity or credentials from baggage. Typed links describe detached work without granting access. Real Node → Python → model fixtures prove canonical parent IDs, child-first delivery and replay.
A local HTTP/file-queue fixture also starts fresh Python worker processes after the producer exits, with concurrent carriers, a crash before claim, detached links and completed-message replay. This qualifies context handoff, not a hosted broker or exactly-once model execution across crashes. A crash after the model returns but before your completion record still needs an application idempotency strategy.
Bun/Deno use the native implementation through Node compatibility APIs. Supply explicit agent/release/service identity in restricted runtimes. For the qualified Workers emulator use nodejs_compat, flushOnExit: false and waitUntil(telemetry.shutdown()) at the request boundary. Host allowances are bounded and cannot guarantee delivery after termination. Each deployment still needs its own lifecycle and stored-trace check.
Verify the first useful trace
Project setup arms a server-time baseline for the selected key, integration, source version and example/application mode. Waiting, partial and expired receipts expose missing evidence. HTTP acknowledgement is admission only. Open the stored run and inspect:
- The selected environment and root with application-owned session/user identity.
- Model/tool parents, with inferred tool steps labelled separately from execution.
- Usage counted once, with absent counts and prices remaining unknown.
- Completed stream outcomes and deliberate failure/cancellation behavior.
- Source/service/version and reported versus computed cost provenance.
A marked example does not complete verification of your own application's action. Inspect export failures separately from model failures; never repeat a billable request solely to resend telemetry. Reported charges are sender-supplied, not audited invoices. Computed costs are estimates; unavailable cost is not free usage.
The official JavaScript OTLP exporter 0.222.0 logs a diagnostic warning for
partialSuccess, but forceFlush() can still resolve when some spans were
rejected. Framework flush is not a typed acceptance result. The downloadable
framework example's relay inspects the response body and fails on partial
rejection; in an existing exporter pipeline inspect diagnostics and use the
processed setup receipt as the proof of capture.
Resolve setup errors
| Symptom | Next step |
|---|---|
| Missing key | Supply TRACEHATCH_API_KEY from setup; never print or commit it. |
| Cannot connect | Check the API origin, traces path, network and API/worker health. |
401 / 403 | Correct the active environment-scoped project ingest key. |
400 / 415 | Use HTTP JSON/protobuf traces with valid IDs/timestamps, not gRPC. |
429 / 503 | Follow exporter backoff and inspect quota/receiver health; do not retry model work. |
| Partial rejection | Inspect exporter diagnostics and fix rejected fields before claiming capture. |
| Root without models/tools | Enable the actual framework hook or instrument the real boundary. |
| Output without usage | Consume streams and request usage where supported; absent counts stay unknown. |
| Accepted, no saved run | Check processing, environment and the exact setup receipt; acknowledgement is not storage proof. |
Capture and privacy limits
Native, Python and third-party OTel policies differ. Configure sender body controls/masking before export; admission redaction protects durable storage but cannot remove text already sent over the network. Third-party instrumentation may capture content independently. Read privacy and data handling before enabling bodies.
Use opaque application identities. Matching prompts are not cross-service correlation, and model-only telemetry cannot time remote tool execution. OpenRouter Broadcast and a model-traffic gateway remain unqualified; the chosen out-of-band boundary is the existing OTLP receiver. Setup requires no model proxy, provider-key store, collector provisioning or deployment.