Skip to content
reaatechREAATECH

@reaatech/otel-genai-semconv-anthropic

npm v0.1.0

Instruments the Anthropic Node.js SDK to automatically emit OpenTelemetry spans compliant with GenAI semantic conventions. It provides a class that wraps the Anthropic client to capture request metadata, token usage, and streaming metrics.

@reaatech/otel-genai-semconv-anthropic

npm version License: MIT CI

Status: Pre-1.0 — APIs may change in minor versions. Pin to a specific version in production.

Transparent instrumentation for the Anthropic Node.js SDK. Wraps client.messages.create() to emit OpenTelemetry GenAI semantic convention spans with request metadata, token usage including prompt caching, cost tracking, and streaming metrics for message delta events.

Installation

terminal
npm install @reaatech/otel-genai-semconv-anthropic
# or
pnpm add @reaatech/otel-genai-semconv-anthropic

Feature Overview

  • Zero-config instrumentation — call instrument(client) once, every messages.create() call is traced
  • Prompt caching awareness — tracks cache_read_input_tokens and cache_creation_input_tokens from Anthropic’s usage metadata
  • Streaming delta aggregation — merges message_start, content_block_delta, and message_delta events into a final Message with accumulated token counts
  • Tool use events — tool call content blocks emit gen_ai.tool_call span events with name and input
  • Double-instrumentation guard — calling instrument() twice is a safe no-op
  • Lifecycle hooksonStart and onEnd callbacks for custom span attributes
  • Safe uninstrument — restores the original create method
  • Dual ESM/CJS output — works with import and require

Quick Start

typescript
import { AnthropicInstrumentation } from "@reaatech/otel-genai-semconv-anthropic";
import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
 
new AnthropicInstrumentation({ trackCosts: true }).instrument(client);
 
const response = await client.messages.create({
  model: "claude-3-opus-20240229",
  max_tokens: 200,
  messages: [{ role: "user", content: "What are the benefits of OpenTelemetry?" }],
});
// Each call now emits OTel spans with gen_ai.* attributes

Captured Attributes

Request Attributes

AttributeSourceDescription
gen_ai.request.modelrequest.modelRequested model name
gen_ai.request.max_tokensrequest.max_tokensMax tokens limit
gen_ai.request.temperaturerequest.temperatureSampling temperature
gen_ai.request.top_prequest.top_pTop-p sampling
gen_ai.request.top_krequest.top_kTop-k sampling
gen_ai.request.streamingrequest.streamStreaming flag
gen_ai.request.stop_sequencesrequest.stop_sequencesStop sequences
gen_ai.request.tool_namesrequest.toolsTool names
gen_ai.provider.namehardcodedanthropic

Response Attributes

AttributeSourceDescription
gen_ai.response.modelresponse.modelActual model used
gen_ai.response.idresponse.idResponse identifier
gen_ai.response.finish_reasonsresponse.stop_reason (mapped)Mapped to OTel finish reason
gen_ai.usage.input_tokensresponse.usage.input_tokensInput token count
gen_ai.usage.output_tokensresponse.usage.output_tokensOutput token count

Stop Reason Mapping

Anthropic’s stop_reason values are mapped to OTel finish_reason:

AnthropicOTel
end_turnstop
stop_sequencestop
max_tokenslength
tool_usetool_calls

Streaming Attributes

AttributeDescription
gen_ai.streaming.time_to_first_token_msLatency to first chunk
gen_ai.streaming.total_duration_msTotal streaming duration
gen_ai.streaming.chunk_countNumber of chunks received

Cost Attributes (when trackCosts: true)

AttributeDescription
llm.cost.totalTotal cost in USD
llm.cost.inputInput token cost
llm.cost.outputOutput token cost
llm.cost.currencyCurrency code (always USD)

Span Events

EventWhen
gen_ai.system.messageSystem prompt in the request
gen_ai.user.messageUser messages in the request
gen_ai.assistant.messageText content blocks in the response
gen_ai.tool_callTool use content blocks (with tool_name, tool_input)

API Reference

AnthropicInstrumentation (class)

Constructor

typescript
new AnthropicInstrumentation({
  captureRequestHeaders?: boolean;
  captureResponseHeaders?: boolean;
  trackCosts?: boolean;
  pricing?: Record<string, PricingInfo>;
  onStart?: (span: Span, request: MessageCreateParams) => void;
  onEnd?: (span: Span, response: Message) => void;
})

Methods

MethodDescription
instrument(client)Wrap client.messages.create() with instrumentation
uninstrument(client)Restore the original create() method

AnthropicTokenCounter (class)

Character-based token estimation for Anthropic models:

typescript
const counter = new AnthropicTokenCounter();
counter.countTokens("Hello, world!", "claude-3-opus-20240229");
counter.countMessagesTokens(messages, "claude-3-opus-20240229");
counter.clearCache();

Attribute Mappers

typescript
import { mapAnthropicRequest, mapAnthropicResponse, mapAnthropicError } from "@reaatech/otel-genai-semconv-anthropic";
 
const requestAttrs = mapAnthropicRequest(messageParams);
const responseAttrs = mapAnthropicResponse(messageObject);
const errorAttrs = mapAnthropicError(apiError);

Configuration

Custom Pricing

typescript
new AnthropicInstrumentation({
  trackCosts: true,
  pricing: {
    "claude-3-opus": { input: 0.015, output: 0.075 },
    "claude-3-sonnet": { input: 0.003, output: 0.015 },
  },
}).instrument(client);

Lifecycle Hooks

typescript
new AnthropicInstrumentation({
  onStart: (span, request) => {
    if (request.metadata?.user_id) {
      span.setAttribute("enduser.id", request.metadata.user_id);
    }
  },
  onEnd: (span, response) => {
    span.setAttribute("response.stop_reason", response.stop_reason);
  },
}).instrument(client);

Usage Patterns

Streaming with Delta Aggregation

The instrumentation automatically aggregates streaming delta events into a final Message:

typescript
const stream = await client.messages.create({
  model: "claude-3-opus-20240229",
  max_tokens: 500,
  messages: [{ role: "user", content: "Write a haiku" }],
  stream: true,
});
 
for await (const event of stream) {
  // Each event type (message_start, content_block_delta, message_delta) is tracked
}
// Span auto-finalizes with aggregated response attributes, tokens, and cost

Multi-Client

typescript
const instrumentation = new AnthropicInstrumentation({ trackCosts: true });
 
const client1 = new Anthropic({ apiKey: "...", baseURL: "..." });
const client2 = new Anthropic({ apiKey: "...", baseURL: "..." });
 
instrumentation.instrument(client1);
instrumentation.instrument(client2);

License

MIT