Anthropic Integration
Add observability to Anthropic Claude API calls with auto-instrumentation or manual context managers.
Quick Start
import waxell_observe
waxell_observe.init(api_key="wax_sk_...", api_url="https://waxell.dev")
# Import Anthropic AFTER init()
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
# Automatically traced with model, tokens, cost
Drop-in Import
Alternative approach using pre-instrumented module:
from waxell_observe.anthropic import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
# Automatically traced
Context Manager Pattern
For multi-step pipelines with full control:
import waxell_observe
waxell_observe.init(api_key="wax_sk_...")
from waxell_observe import WaxellContext, generate_session_id
from waxell_observe.errors import PolicyViolationError
import anthropic
client = anthropic.AsyncAnthropic()
async def analyze_content(query: str, user_id: str = ""):
session = generate_session_id()
async with WaxellContext(
agent_name="anthropic-demo",
workflow_name="content-analysis",
inputs={"query": query},
session_id=session,
user_id=user_id,
user_group="enterprise",
enforce_policy=True,
mid_execution_governance=True,
) as ctx:
# Add tags for categorization
ctx.set_tag("demo", "anthropic")
ctx.set_tag("provider", "anthropic")
ctx.set_metadata("sdk", "anthropic-python")
try:
# Step 1: Classify content
classify_response = await client.messages.create(
model="claude-sonnet-4",
max_tokens=500,
messages=[{
"role": "user",
"content": f"Classify this text: {query}"
}],
)
classification = classify_response.content[0].text
ctx.record_step("classify_content", output={
"classification": classification[:200]
})
ctx.record_llm_call(
model=classify_response.model,
tokens_in=classify_response.usage.input_tokens,
tokens_out=classify_response.usage.output_tokens,
task="classify_content",
prompt_preview=query[:200],
response_preview=classification[:200],
)
# Step 2: Extract entities
extract_response = await client.messages.create(
model="claude-sonnet-4",
max_tokens=500,
messages=[{
"role": "user",
"content": f"Extract key entities from: {query}"
}],
)
entities = extract_response.content[0].text
ctx.record_step("extract_entities", output={
"entities": entities[:200]
})
ctx.record_llm_call(
model=extract_response.model,
tokens_in=extract_response.usage.input_tokens,
tokens_out=extract_response.usage.output_tokens,
task="extract_entities",
)
# Step 3: Summarize
summary_response = await client.messages.create(
model="claude-sonnet-4",
max_tokens=500,
messages=[{
"role": "user",
"content": f"Summarize:\n\nText: {query}\nClassification: {classification}\nEntities: {entities}"
}],
)
summary = summary_response.content[0].text
ctx.record_step("summarize", output={
"summary_length": len(summary)
})
ctx.record_llm_call(
model=summary_response.model,
tokens_in=summary_response.usage.input_tokens,
tokens_out=summary_response.usage.output_tokens,
task="summarize",
)
ctx.set_result({
"classification": classification,
"entities": entities,
"summary": summary,
})
return {
"classification": classification,
"entities": entities,
"summary": summary,
}
except PolicyViolationError as e:
print(f"Policy violation: {e}")
raise
# Run it
result = await analyze_content(
"Analyze the impact of AI on healthcare",
user_id="user_789"
)
Recording LLM Calls
Anthropic's usage object has different field names than OpenAI:
# Anthropic response
response = await client.messages.create(...)
ctx.record_llm_call(
model=response.model,
tokens_in=response.usage.input_tokens, # Note: input_tokens
tokens_out=response.usage.output_tokens, # Note: output_tokens
task="my_task",
prompt_preview=prompt[:200],
response_preview=response.content[0].text[:200],
)
Streaming with Anthropic
Anthropic uses events instead of chunks:
async with WaxellContext(agent_name="streaming-claude") as ctx:
stream = await client.messages.create(
model="claude-sonnet-4",
max_tokens=500,
messages=[{"role": "user", "content": query}],
stream=True,
)
content = ""
tokens_in = 0
tokens_out = 0
async for event in stream:
if event.type == "content_block_delta" and hasattr(event.delta, "text"):
content += event.delta.text
elif event.type == "message_start" and hasattr(event, "message"):
if hasattr(event.message, "usage"):
tokens_in = event.message.usage.input_tokens
elif event.type == "message_delta" and hasattr(event, "usage"):
if event.usage:
tokens_out = event.usage.output_tokens
ctx.record_step("stream_response", output={"length": len(content)})
ctx.record_llm_call(
model="claude-sonnet-4",
tokens_in=tokens_in,
tokens_out=tokens_out,
response_preview=content[:200],
)
Supported Models
| Model | Auto-Instrumented | Cost Tracking |
|---|---|---|
| claude-opus-4 | Yes | Yes |
| claude-sonnet-4 | Yes | Yes |
| claude-3-5-sonnet | Yes | Yes |
| claude-3-5-haiku | Yes | Yes |
| claude-3-haiku | Yes | Yes |
Tags and Metadata
Enrich traces with contextual information:
async with WaxellContext(agent_name="claude-agent") as ctx:
# Tags: searchable in UI
ctx.set_tag("provider", "anthropic")
ctx.set_tag("model_tier", "premium")
ctx.set_tag("use_case", "analysis")
# Metadata: arbitrary context
ctx.set_metadata("sdk_version", "0.25.0")
ctx.set_metadata("config", {"max_tokens": 500, "temperature": 0.7})
response = await client.messages.create(...)
Error Handling
from waxell_observe.errors import PolicyViolationError
try:
async with WaxellContext(
agent_name="claude-agent",
enforce_policy=True,
) as ctx:
response = await client.messages.create(...)
except PolicyViolationError as e:
# Policy blocked execution (budget, rate limit, etc.)
print(f"Blocked: {e.policy_result.reason}")
except anthropic.APIError as e:
# Anthropic API error
print(f"API error: {e}")
Best Practices
- Call
init()before importing anthropic -- enables auto-instrumentation - Use
input_tokensandoutput_tokens-- Anthropic's field names - Handle streaming events by type --
content_block_delta,message_start,message_delta - Set max_tokens -- Required by Anthropic API
- Add provider tag -- Makes filtering easy in the UI
Next Steps
- Streaming Integration -- Detailed streaming patterns
- LiteLLM Integration -- Use Anthropic via LiteLLM
- Multi-Agent -- Coordinate Claude agents