Skip to content

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

RouteExact local qualificationCapture boundary
Native SDKNode 22.22.0, Windows; provider versions in the method matrixExplicit client wrapping, measured models/tools and separately labelled inferred tool steps
AI SDKai 7.0.107, @ai-sdk/openai 4.0.71, @ai-sdk/otel 1.0.107; Node 22.22.0Framework model/tool spans; outer wrapper usage is counted on model children only
LangChainCore 1.2.11, OpenAI 1.5.13, OpenInference 4.1.1; Node 22.22.0Awaited callback instrumentation for the tested model/tool paths
Pythontracehatch-python 0.1.0, openai 3.16.2, OTel 1.44.0; Windows CPython 3.11.14 and 3.13.0Explicit sync/async Chat Completions, streams, trace/tool contexts
.NETOpenAI 2.14.0, OTel 1.19.0; Windows .NET 10.0.12 / SDK 10.0.401Explicit Chat Completions, async streams, task context and tools
Goopenai-go/v3 3.63.1, OTel 1.46.0; Windows Go 1.27.1Explicit Chat Completions, streams, goroutine context and tools
JavaOpenAI Java 4.65.0, OTel 1.66.0; Windows Microsoft OpenJDK 25.0.4.1 / Maven 3.9.12Explicit Chat Completions, streams, executor context and tools
Bun / DenoBun 1.4.2 and Deno 2.9.6, Windows; OpenAI 7.15.0Shared native implementation through Node compatibility APIs; local only
Workersworkerd 1.20260730.1 / Miniflare 4.20260730.0, compatibility date 2026-07-30 and nodejs_compat; OpenAI 7.15.0Local 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:

Terminal
npm install ./tracehatch-sdk-0.15.0.tgz

Use 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.

TypeScript
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:

TypeScript
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:

Terminal
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 --export

The 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:

TypeScript
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:

Terminal
npm run langchain
node run.mjs langchain --scenario success --export

The 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.

TypeScript
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:

Terminal
tar -xzf python-otlp.tar.gz
cd python-otlp
python -m venv .venv

Activate .venv\Scripts\Activate.ps1 in PowerShell or source .venv/bin/activate on POSIX, then:

Terminal
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 success

configure 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.

Python
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:

Terminal
dotnet restore --locked-mode
dotnet run -- --fixture --record recorded
# With the project key and complete OTLP traces endpoint:
dotnet run -- --fixture --export --scenario success

Explicit 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:

Terminal
go mod download
go run . --fixture --record recorded
go run . --fixture --export --scenario success

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

Terminal
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:

  1. The selected environment and root with application-owned session/user identity.
  2. Model/tool parents, with inferred tool steps labelled separately from execution.
  3. Usage counted once, with absent counts and prices remaining unknown.
  4. Completed stream outcomes and deliberate failure/cancellation behavior.
  5. 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

SymptomNext step
Missing keySupply TRACEHATCH_API_KEY from setup; never print or commit it.
Cannot connectCheck the API origin, traces path, network and API/worker health.
401 / 403Correct the active environment-scoped project ingest key.
400 / 415Use HTTP JSON/protobuf traces with valid IDs/timestamps, not gRPC.
429 / 503Follow exporter backoff and inspect quota/receiver health; do not retry model work.
Partial rejectionInspect exporter diagnostics and fix rejected fields before claiming capture.
Root without models/toolsEnable the actual framework hook or instrument the real boundary.
Output without usageConsume streams and request usage where supported; absent counts stay unknown.
Accepted, no saved runCheck 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.