Anthropic
Auto-instrument the Anthropic SDK. Every messages.create or messages.stream call inside an active run emits an llm_call event with model, token usage, prompt-cache counts, latency, stop reason, and tool use. Cost is computed server-side from the rate card.
Install / enable
Python:
pip install "agentping-io[anthropic]"
import agentping
import anthropic
agentping.init()
agentping.instrument_anthropic()
client = anthropic.Anthropic()
with agentping.run("support-triage", customer_id="acme-corp") as r:
reply = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "summarise this ticket"}],
)
r.finish(scores={"confidence": 0.93})
instrument_anthropic() patches anthropic.Anthropic and anthropic.AsyncAnthropic at the class level, so clients created before or after the call are covered. Idempotent and global; call it once at module load. Supported anthropic versions: 0.30 up to, but not including, 2.0. Outside that range the patch is skipped with a single warning.
TypeScript:
npm install @agentping/sdk @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
import * as agentping from "@agentping/sdk";
agentping.init({ apiKey: process.env.AGENTPING_API_KEY });
const run = agentping.run("answer-bot");
const anthropic = agentping.instrumentAnthropic(new Anthropic(), { run });
const reply = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "hi" }],
});
await run.finish({ status: "success" });
instrumentAnthropic returns a wrapped client whose return types match the original. No global patching; the wrapper holds the run reference, or resolves the active run from runScopeAsync when { run } is omitted.
What's captured
| Field | Source |
|---|---|
provider |
anthropic |
model |
response.model |
input_tokens |
usage.input_tokens plus cache_read_input_tokens plus cache_creation_input_tokens. Anthropic reports the prompt net of the cache; the SDK reports the gross count and ingest does the split. |
output_tokens |
usage.output_tokens |
cached_input_tokens |
usage.cache_read_input_tokens |
cache_creation_input_tokens |
usage.cache_creation_input_tokens |
finish_reason |
stop_reason |
tool_calls |
Number of tool_use content blocks in the response |
latency_ms |
Wall-clock time of the call |
stream |
true on streamed calls |
Anthropic does not report reasoning tokens separately, so reasoning_tokens is never set. Zero-valued fields are omitted.
A call that raises (rate limit, timeout, invalid request) is recorded as an llm_call with status: "error", error (the message) and exception (the class name), then re-raised. Tool-use exchanges stay one llm_call per round trip; the tool executions themselves are recorded by the agent framework or by your own tool_call events:
r.event("tool_call", {"tool": "fetch_orders", "status": "success", "latency_ms": 212})
Streaming
Both SDKs capture messages.create(stream=True) and the messages.stream() helper. The llm_call fires once the stream is fully consumed or closed, with the final usage figures and stream: true.
with agentping.run("answer-bot"):
with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "explain in one sentence"}],
) as stream:
for chunk in stream.text_stream:
print(chunk, end="", flush=True)
# llm_call is emitted here, when the context manager exits
A stream that fails part way through is recorded as an errored llm_call with stream: true.
Prompt capture and time to first token
Streamed calls also report ttft_ms, the time from the request to the first content_block_start or content_block_delta event; message_start does not count. Pulse charts its p95 per agent. Python's messages.stream() context manager is the exception: the SDK iterates the stream itself, so that path reports usage but no ttft_ms. The Go transport measures it from the first SSE content event.
Prompt and completion text are off by default. Turn them on once at init, with an optional cap per piece (default 4000 characters):
agentping.init(capture_llm_content=True, llm_content_max_chars=4000)
agentping.init({ captureLlmContent: true, llmContentMaxChars: 4000 });
Each llm_call then carries input (the messages list as {role, content}), system_instructions (the system parameter, text blocks joined) and output (the response's text blocks). Images, tool_use and tool_result blocks appear as markers such as [tool_use lookup], and each piece is clipped at the cap. The run page renders them as messages; see events for the wire shape. In Go, pass instrumentation.WithContentCapture(4000) to the transport instead.
Source / notes
- Python:
agentping.instrument_anthropic()in agent-ping-python - TypeScript:
instrumentAnthropicin agent-ping-typescript
Calling Claude through Bedrock? Use the Bedrock wrapper; the Anthropic wrapper only sees the Anthropic client. Unknown models still land with their token counts and appear in the unpriced-models list until you add a rate under Spend, Rate cards. See Spend.