Anthropic Integration
Trace Anthropic Claude API calls with Mantis SDK, including messages, tool use, and token counts.
Overview
Mantis SDK auto-instruments the Anthropic Python SDK to capture:
- Message creation calls
- Tool/function calls
- Token usage (input/output)
- Model and parameters
Installation
pip install mantisdk anthropicQuick Start
from dotenv import load_dotenv
load_dotenv()
# Initialize tracing FIRST
import mantisdk.tracing as tracing
tracing.init(service_name="anthropic-agent", instrument_default=False)
tracing.instrument(names=["anthropic"])
# NOW import Anthropic
import anthropic
# Create client and use - automatically traced
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude!"}]
)
print(response.content[0].text)
# Clean up
tracing.shutdown()Tracing Features
Auto-Captured Data
Every messages.create() call automatically captures:
| Attribute | Description |
|---|---|
| Model | Model name (e.g., claude-sonnet-4-20250514) |
| Input tokens | Token count for input |
| Output tokens | Token count for output |
| Messages | Input messages |
| Response | Generated response |
| Tool calls | Tool use blocks (if any) |
| Latency | Request duration |
Tool Use Tracing
Tool calls are captured as part of the span:
import anthropic
import mantisdk.tracing as tracing
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}]
)
# Tool calls are automatically tracedComplete Example: Task Planner Agent
This example demonstrates a task management agent with tool use:
#!/usr/bin/env python3
"""Anthropic Task Planner Agent with Mantis SDK Tracing."""
import json
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List, Optional
from dotenv import load_dotenv
load_dotenv()
# Initialize tracing FIRST
import mantisdk.tracing as tracing
from mantisdk.tracing.exporters.insight import is_insight_configured
def setup_tracing(trace_to_console: bool = False):
"""Initialize mantisdk tracing with Anthropic instrumentation."""
exporters = []
if is_insight_configured():
exporters.append(tracing.insight_exporter())
print("[Tracing] Insight exporter configured")
else:
print("[Tracing] No Insight credentials found")
if trace_to_console:
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
exporters.append(ConsoleSpanExporter())
tracing.init(
service_name="anthropic-task-planner",
exporters=exporters if exporters else None,
instrument_default=False,
)
tracing.instrument(names=["anthropic"])
print("[Tracing] Anthropic instrumentation enabled")
# NOW import Anthropic
import anthropic
# Task data model
@dataclass
class Task:
id: str
title: str
description: str
status: str = "pending"
priority: str = "medium"
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
# In-memory task store
class TaskStore:
def __init__(self):
self.tasks: Dict[str, Task] = {}
def create_task(self, title: str, description: str, priority: str = "medium") -> Task:
import uuid
task_id = str(uuid.uuid4())[:8]
task = Task(id=task_id, title=title, description=description, priority=priority)
self.tasks[task_id] = task
return task
def list_tasks(self, status: Optional[str] = None) -> List[Task]:
tasks = list(self.tasks.values())
if status:
tasks = [t for t in tasks if t.status == status]
return tasks
def complete_task(self, task_id: str) -> Optional[Task]:
task = self.tasks.get(task_id)
if task:
task.status = "completed"
return task
task_store = TaskStore()
# Tool implementations
def create_task(title: str, description: str, priority: str = "medium") -> dict:
task = task_store.create_task(title, description, priority)
return {"status": "success", "task": {"id": task.id, "title": task.title}}
def list_tasks(status: Optional[str] = None) -> dict:
tasks = task_store.list_tasks(status)
return {"status": "success", "tasks": [{"id": t.id, "title": t.title} for t in tasks]}
def complete_task(task_id: str) -> dict:
task = task_store.complete_task(task_id)
if task:
return {"status": "success", "message": f"Completed: {task.title}"}
return {"status": "error", "error_message": f"Task {task_id} not found"}
TOOL_FUNCTIONS = {
"create_task": create_task,
"list_tasks": list_tasks,
"complete_task": complete_task,
}
TOOLS = [
{
"name": "create_task",
"description": "Create a new task",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]}
},
"required": ["title", "description"]
}
},
{
"name": "list_tasks",
"description": "List all tasks",
"input_schema": {
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["pending", "completed"]}
}
}
},
{
"name": "complete_task",
"description": "Mark a task as completed",
"input_schema": {
"type": "object",
"properties": {"task_id": {"type": "string"}},
"required": ["task_id"]
}
}
]
class TaskPlannerAgent:
"""Anthropic-based task planner with tool use."""
def __init__(self, model: str = "claude-sonnet-4-20250514"):
self.client = anthropic.Anthropic()
self.model = model
self.messages = []
self.system = "You are a task planning assistant. Help users manage tasks."
async def chat(self, user_message: str) -> str:
"""Process a user message - auto-instrumented."""
self.messages.append({"role": "user", "content": user_message})
# This call is auto-instrumented
response = self.client.messages.create(
model=self.model,
max_tokens=1024,
system=self.system,
messages=self.messages,
tools=TOOLS,
)
if response.stop_reason == "tool_use":
return await self._handle_tool_use(response)
text = "".join(b.text for b in response.content if hasattr(b, "text"))
self.messages.append({"role": "assistant", "content": response.content})
return text
async def _handle_tool_use(self, response) -> str:
"""Handle tool calls."""
self.messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
func = TOOL_FUNCTIONS.get(block.name)
if func:
result = func(**block.input)
else:
result = {"status": "error", "error_message": "Unknown tool"}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
self.messages.append({"role": "user", "content": tool_results})
# Follow-up call - also auto-instrumented
final = self.client.messages.create(
model=self.model,
max_tokens=1024,
system=self.system,
messages=self.messages,
tools=TOOLS,
)
if final.stop_reason == "tool_use":
return await self._handle_tool_use(final)
text = "".join(b.text for b in final.content if hasattr(b, "text"))
self.messages.append({"role": "assistant", "content": final.content})
return text
async def main():
import asyncio
setup_tracing(trace_to_console=False)
agent = TaskPlannerAgent()
# Example conversation
queries = [
"Create a task to review the Q4 report",
"List all my tasks",
]
for query in queries:
print(f"\nUser: {query}")
response = await agent.chat(query)
print(f"Assistant: {response}")
tracing.shutdown()
if __name__ == "__main__":
import asyncio
asyncio.run(main())Trace Hierarchy
The example produces traces like:
chat (function call)
├── anthropic.messages.create (auto-instrumented)
│ ├── model: claude-sonnet-4-20250514
│ ├── input_tokens: 180
│ ├── output_tokens: 45
│ └── tool_use: create_task
└── anthropic.messages.create (follow-up)
├── model: claude-sonnet-4-20250514
├── input_tokens: 250
└── output_tokens: 30Adding Manual Spans
Combine auto-instrumentation with manual tracing:
import anthropic
import mantisdk.tracing as tracing
client = anthropic.Anthropic()
def analyze_text(text: str) -> dict:
with tracing.trace("analyze_text", input=text) as span:
span.set_attribute(tracing.semconv.USER_ID, "user-123")
# Preprocess
with tracing.span("preprocess"):
cleaned = preprocess(text)
# LLM call - auto-instrumented as child span
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": cleaned}]
)
result = response.content[0].text
span.set_attribute(tracing.semconv.TRACE_OUTPUT, result)
return {"analysis": result}Next Steps
- Examples: Task Planner - Full example code
- Google ADK Integration - Google ADK
- Auto-Instrumentation - How it works