Anthropic
Trace Anthropic Messages API calls, token usage, tools, thinking, and streams from TypeScript or Python.
Wrap your Anthropic client, and each Messages API call you make with it becomes a span in telemetry.dev. Your calls and their return values don’t change.
To trace requests on Anthropic’s side instead, with no code changes, see Claude Platform. To use the Claude SDK’s own OpenTelemetry spans instead of this wrapper, see Claude SDK; don’t use the wrapper and those spans together.
What you get
- Messages, system instructions, tool definitions, tool choice, model, and sampling parameters
- Response IDs, response models, stop reasons, content blocks, errors, and time to first chunk
- Input, output, cache-read, cache-write, and thinking token counts
- Text, tool input, citations, thinking, and signatures rebuilt from stream deltas
- Calls made through
beta.messages,messages.parse(), and the SDK tool runner
Span input tokens include cache reads and cache writes, following the OpenTelemetry convention. Anthropic’s own input_tokens leaves them out. Anthropic reports no total token count, and the integration doesn’t calculate one.
Install
npm i @telemetry-dev/sdk @telemetry-dev/anthropic @anthropic-ai/sdkpip install telemetry-dev-anthropicThe Python package also installs telemetry-dev and anthropic.
| Language | Requirements |
|---|---|
| TypeScript | Node.js >=20.19.0, @telemetry-dev/sdk ^0.1.8, @anthropic-ai/sdk >=0.110.0 <1 |
| Python | Python >=3.10, telemetry-dev >=0.2.9, anthropic >=0.116,<2 |
Trace a call
Set your telemetry.dev project API key and your Anthropic key. Copy the telemetry.dev key from the project setup page; it starts with td_live_.
export TELEMETRY_DEV_API_KEY=td_live_...
export ANTHROPIC_API_KEY=sk-ant-...
Call init(), wrap the client, and use it as usual:
import Anthropic from "@anthropic-ai/sdk";
import { flush, init, shutdown } from "@telemetry-dev/sdk";
import { wrapAnthropic } from "@telemetry-dev/anthropic";
init({ serviceName: "anthropic-app" });
const anthropic = wrapAnthropic(new Anthropic());
try {
const message = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 200,
system: "You are a helpful bot",
messages: [
{ role: "user", content: "Tell me a joke about OpenTelemetry" },
],
});
console.log(
message.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("\n"),
);
} finally {
await flush();
await shutdown();
}import telemetry_dev
from anthropic import Anthropic
from telemetry_dev_anthropic import wrap_anthropic
telemetry_dev.init(service_name="anthropic-app")
client = wrap_anthropic(Anthropic())
try:
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=200,
system="You are a helpful bot",
messages=[
{"role": "user", "content": "Tell me a joke about OpenTelemetry"},
],
)
print("\n".join(block.text for block in message.content if block.type == "text"))
finally:
telemetry_dev.flush()
telemetry_dev.shutdown()Run the script, then open the trace explorer. The trace has one chat claude-sonnet-4-6 generation span with the messages, the reply, and token usage.
Without an API key, init() turns telemetry off and the wrapped client works as before.
Instrument all clients
To trace every client without wrapping each one, call the global function once at startup:
import { instrumentAnthropic } from "@telemetry-dev/anthropic";
instrumentAnthropic();from telemetry_dev_anthropic import instrument_anthropic
instrument_anthropic()In TypeScript, global instrumentation patches the ES module build of the @anthropic-ai/sdk copy that this package resolves. It misses clients created with require(), which use the CommonJS build. It can also miss the Bedrock, Vertex, AWS, and Foundry clients, because their packages can install a separate copy of @anthropic-ai/sdk. Wrap those clients with wrapAnthropic().
API
| TypeScript | Python | What it does |
|---|---|---|
wrapAnthropic(client) |
wrap_anthropic(client) |
Instruments messages and beta.messages on one client and returns it. Wrapping the same client again has no effect. Python accepts synchronous and asynchronous clients. |
instrumentAnthropic() |
instrument_anthropic() |
Instruments every client through the stable and beta Messages classes. |
uninstrumentAnthropic() |
uninstrument_anthropic() |
Removes global instrumentation. |
These functions take no options.
Traced calls
Each call produces one chat {model} generation span.
| Anthropic call | Notes |
|---|---|
messages.create() |
Includes stream: true (stream=True in Python). |
messages.stream() |
In TypeScript, the helper goes through create(). In Python, the span starts when you enter the context manager, because that is when the SDK opens the HTTP stream. |
messages.parse() |
One span for each request. |
beta.messages.create(), stream(), and parse() |
Beta features such as context management and fast mode produce the same spans as messages. |
Tool runner: beta.messages.toolRunner() in TypeScript, beta.messages.tool_runner() in Python |
The runner sends one request for each model turn, and each turn gets its own span. To group the turns, run the tool runner inside an agent span. |
The integration doesn’t trace messages.countTokens() (count_tokens() in Python), beta.messages.countTokens(), Message Batches, or other non-Messages endpoints.
In TypeScript, traced calls keep asResponse() and withResponse(). Calling either one also records the HTTP status code and the request-id header on the span. The withRawResponse namespace isn’t instrumented separately.
In Python, requests sent through with_raw_response or with_streaming_response aren’t traced. The integration skips any request whose X-Stainless-Raw-Response header asks for a raw response.
Streaming
A stream span stays open until your code reads the stream to the end, closes it, or the stream fails. In Python, leaving the messages.stream() context manager also ends it. A stream you never read never finishes its span. Stream events reach your code unchanged.
The span combines usage from message_start and message_delta, plus text, citations, tool-input JSON, thinking, and signature deltas. If the final tool input isn’t valid JSON, it stays as the raw string. The span records the time to the first event.
In TypeScript, aborting a request before you read its stream ends the span with an error.
Provider names
The span’s gen_ai.provider.name comes from the client class, including subclasses:
| Client | Provider |
|---|---|
AnthropicBedrock, AnthropicBedrockMantle (and their Async variants in Python) |
aws.bedrock |
AnthropicVertex (and AsyncAnthropicVertex) |
gcp.vertex_ai |
| Any other client, including Claude Platform on AWS and Microsoft Foundry clients | anthropic |
Captured content
Prompts and responses follow the core SDK’s captureInput, captureOutput, and mask settings (capture_input, capture_output, and mask in Python). Tools and tool_choice are part of the input, so turning input capture off removes them too. With output capture off, response IDs, models, stop reasons, usage, timing, and errors are still recorded.
The capture settings don’t cover stop sequences, metadata and attributes you pass yourself, or exception messages and stack traces. Redact those separately if they can hold sensitive data. See privacy for server-side redaction.
Send telemetry before exit
In a short script, call flush() and then shutdown() before the process exits, as in the example above. In serverless code, call flush() before the handler returns. In TypeScript you can pass a waitUntil function to init() instead. See the TypeScript SDK and Python SDK guides for details.