The wrap_anthropic() function wraps an Anthropic client to automatically trace all API calls to LangSmith.
Basic usage
from langsmith import wrappers
import anthropic
# Wrap the Anthropic client
client = wrappers.wrap_anthropic(anthropic.Anthropic())
# All API calls are now automatically traced
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
Function signature
def wrap_anthropic(
client: Anthropic | AsyncAnthropic,
*,
project_name: str | None = None,
tags: list[str] | None = None,
metadata: dict[str, Any] | None = None,
client_: Client | None = None
) -> Anthropic | AsyncAnthropic:
...
Parameters
client
Anthropic | AsyncAnthropic
required
The Anthropic client instance to wrap. Can be sync or async.import anthropic
# Sync client
client = wrappers.wrap_anthropic(anthropic.Anthropic())
# Async client
async_client = wrappers.wrap_anthropic(anthropic.AsyncAnthropic())
LangSmith project to log traces to.client = wrappers.wrap_anthropic(
anthropic.Anthropic(),
project_name="anthropic-traces"
)
Tags to attach to all traces from this client.client = wrappers.wrap_anthropic(
anthropic.Anthropic(),
tags=["production", "claude-3-5"]
)
Metadata to attach to all traces.client = wrappers.wrap_anthropic(
anthropic.Anthropic(),
metadata={"environment": "prod"}
)
Custom LangSmith client to use for tracing.
return
Anthropic | AsyncAnthropic
The wrapped client with tracing enabled.
What gets traced
The wrapper automatically traces:
- Messages API:
client.messages.create()
- Streaming responses: Both sync and async streaming
- Token usage: Input/output tokens and cache statistics
- Model parameters: Temperature, max_tokens, top_p, etc.
- Tool use: Tool calls and responses
- Thinking blocks: Extended thinking with Claude 3.7 Sonnet
- Error handling: Failed requests are traced with error info
Examples
Basic message
from langsmith import wrappers
import anthropic
client = wrappers.wrap_anthropic(anthropic.Anthropic())
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": "What is LangSmith?"}
]
)
print(response.content[0].text)
# Automatically traced to LangSmith
System prompts
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system="You are a helpful AI assistant.",
messages=[
{"role": "user", "content": "Hello!"}
]
)
# System prompt is included in trace
Streaming responses
client = wrappers.wrap_anthropic(anthropic.Anthropic())
with client.messages.stream(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": "Write a haiku"}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
# Full stream is collected and traced
Async client
import asyncio
from langsmith import wrappers
import anthropic
async_client = wrappers.wrap_anthropic(anthropic.AsyncAnthropic())
async def generate():
response = await async_client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
return response.content[0].text
result = asyncio.run(generate())
Async streaming
async_client = wrappers.wrap_anthropic(anthropic.AsyncAnthropic())
async def stream_generate():
async with async_client.messages.stream(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": "Count to 10"}]
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
await stream_generate()
client = wrappers.wrap_anthropic(anthropic.Anthropic())
tools = [
{
"name": "get_weather",
"description": "Get weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
]
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in SF?"}]
)
# Tool calls are traced with full details
if response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")
Extended thinking (Claude 3.7 Sonnet)
client = wrappers.wrap_anthropic(anthropic.Anthropic())
response = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=4000,
thinking={
"type": "enabled",
"budget_tokens": 2000
},
messages=[
{"role": "user", "content": "Solve this complex math problem..."}
]
)
# Thinking blocks are captured in the trace
for block in response.content:
if block.type == "thinking":
print(f"Thinking: {block.thinking}")
elif block.type == "text":
print(f"Answer: {block.text}")
Within traced functions
from langsmith import traceable, wrappers
import anthropic
client = wrappers.wrap_anthropic(anthropic.Anthropic())
@traceable
def answer_question(question: str) -> str:
# Anthropic call is traced as a child of answer_question
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": question}]
)
return response.content[0].text
answer = answer_question("What is 2+2?")
# Creates nested trace: answer_question > messages.create
Prompt caching
client = wrappers.wrap_anthropic(anthropic.Anthropic())
# Large context with caching
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system=[
{
"type": "text",
"text": "Very long system prompt...",
"cache_control": {"type": "ephemeral"}
}
],
messages=[{"role": "user", "content": "Question about the context"}]
)
# Cache statistics are included in trace metadata
print(f"Cache read tokens: {response.usage.cache_read_input_tokens}")
print(f"Cache creation tokens: {response.usage.cache_creation_input_tokens}")
The wrapper automatically captures:
- Model name: e.g.,
"claude-3-5-sonnet-20241022"
- Provider:
"anthropic"
- Model type:
"chat"
- Temperature: If specified
- Max tokens: Token limit
- Top-p/Top-k: If specified
- Token usage: Including cache read/creation statistics
- Stop reason: Why generation stopped
- Thinking tokens: For extended thinking mode
- Invocation parameters: Request parameters
Integration with testing
Use with @test decorator for cached testing:
import pytest
from langsmith import test, wrappers
import anthropic
# Set LANGSMITH_TEST_CACHE=tests/cassettes
client = wrappers.wrap_anthropic(anthropic.Anthropic())
@test(cached_hosts=["api.anthropic.com"])
def test_anthropic_response():
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=100,
messages=[{"role": "user", "content": "Say hello"}]
)
assert "hello" in response.content[0].text.lower()
# First run: real API call (cached)
# Subsequent runs: instant (from cache)
Best practices
-
Wrap once at initialization: Don’t wrap repeatedly
# Good
client = wrappers.wrap_anthropic(anthropic.Anthropic())
# Bad
def my_function():
client = wrappers.wrap_anthropic(anthropic.Anthropic()) # Don't
-
Use with environment variables: Configure globally
import os
os.environ["LANGCHAIN_PROJECT"] = "my-project"
client = wrappers.wrap_anthropic(anthropic.Anthropic())
-
Combine with @traceable: For nested traces
@traceable
def my_pipeline(input: str):
result = client.messages.create(...) # Traced as child
return result
-
Handle errors gracefully: Errors are automatically traced
try:
response = client.messages.create(...)
except anthropic.APIError as e:
# Error is captured in trace
print(f"API error: {e}")