Anthropic
Copy page as Markdown
View page source on GitHub
Ask OpenAI about this page
Ask Claude about this page
Log Anthropic Claude API calls, responses, and token usage to Parseable for LLM observability.
Overview
Integrate Anthropic with Parseable to:
- API Logging - Track all Claude API calls
- Token Usage - Monitor input/output tokens
- Latency Tracking - Measure response times
- Error Analysis - Debug failed requests
- Model Comparison - Compare Claude model performance
Prerequisites
- Anthropic API key
- Parseable instance accessible
- Python application
Python Integration
Basic Logger
import anthropic
import requests
import time
from datetime import datetime
from typing import Dict, Any, List
class AnthropicLogger:
def __init__(self, parseable_url: str, dataset: str, username: str, password: str):
self.parseable_url = parseable_url
self.dataset = dataset
self.auth = (username, password)
self.client = anthropic.Anthropic()
def _log(self, entry: Dict[str, Any]):
try:
requests.post(
f"{self.parseable_url}/api/v1/ingest",
json=[entry],
auth=self.auth,
headers={"X-P-Stream": self.dataset},
timeout=5
)
except Exception as e:
print(f"Logging failed: {e}")
def message(
self,
messages: List[Dict],
model: str = "claude-3-opus-20240229",
max_tokens: int = 1024,
system: str = None,
**kwargs
) -> Any:
start_time = datetime.utcnow()
log_entry = {
"timestamp": start_time.isoformat() + "Z",
"provider": "anthropic",
"type": "message",
"model": model,
"max_tokens": max_tokens,
"message_count": len(messages),
"has_system": system is not None,
"user_prompt_preview": messages[-1]["content"][:200] if messages else None
}
try:
response = self.client.messages.create(
model=model,
max_tokens=max_tokens,
system=system,
messages=messages,
**kwargs
)
end_time = datetime.utcnow()
log_entry.update({
"success": True,
"duration_ms": (end_time - start_time).total_seconds() * 1000,
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens,
"total_tokens": response.usage.input_tokens + response.usage.output_tokens,
"stop_reason": response.stop_reason,
"response_preview": response.content[0].text[:200] if response.content else None
})
self._log(log_entry)
return response
except Exception as e:
log_entry.update({
"success": False,
"error": str(e),
"error_type": type(e).__name__
})
self._log(log_entry)
raise
# Usage
logger = AnthropicLogger(
parseable_url="http://parseable:8000",
dataset="anthropic-logs",
username="admin",
password="admin"
)
response = logger.message(
messages=[{"role": "user", "content": "Explain quantum computing in simple terms."}],
model="claude-3-sonnet-20240229",
system="You are a helpful science teacher."
)Streaming Support
def message_stream(
self,
messages: List[Dict],
model: str = "claude-3-opus-20240229",
max_tokens: int = 1024,
**kwargs
):
start_time = datetime.utcnow()
log_entry = {
"timestamp": start_time.isoformat() + "Z",
"provider": "anthropic",
"type": "message_stream",
"model": model,
"max_tokens": max_tokens
}
try:
with self.client.messages.dataset(
model=model,
max_tokens=max_tokens,
messages=messages,
**kwargs
) as dataset:
full_response = ""
for text in dataset.text_stream:
full_response += text
yield text
# Get final message for usage stats
final_message = dataset.get_final_message()
log_entry.update({
"success": True,
"duration_ms": (datetime.utcnow() - start_time).total_seconds() * 1000,
"input_tokens": final_message.usage.input_tokens,
"output_tokens": final_message.usage.output_tokens,
"response_length": len(full_response)
})
except Exception as e:
log_entry.update({
"success": False,
"error": str(e)
})
raise
finally:
self._log(log_entry)Node.js Integration
const Anthropic = require('@anthropic-ai/sdk');
const axios = require('axios');
class AnthropicLogger {
constructor(parseableUrl, dataset, auth) {
this.parseableUrl = parseableUrl;
this.dataset = dataset;
this.auth = auth;
this.client = new Anthropic();
}
async log(entry) {
try {
await axios.post(`${this.parseableUrl}/api/v1/ingest`, [entry], {
headers: {
'Authorization': `Basic ${this.auth}`,
'X-P-Stream': this.dataset
}
});
} catch (error) {
console.error('Logging failed:', error.message);
}
}
async message(messages, options = {}) {
const startTime = Date.now();
const model = options.model || 'claude-3-sonnet-20240229';
const logEntry = {
timestamp: new Date().toISOString(),
provider: 'anthropic',
type: 'message',
model,
message_count: messages.length
};
try {
const response = await this.client.messages.create({
model,
max_tokens: options.max_tokens || 1024,
messages,
...options
});
logEntry.success = true;
logEntry.duration_ms = Date.now() - startTime;
logEntry.input_tokens = response.usage?.input_tokens;
logEntry.output_tokens = response.usage?.output_tokens;
logEntry.stop_reason = response.stop_reason;
await this.log(logEntry);
return response;
} catch (error) {
logEntry.success = false;
logEntry.error = error.message;
await this.log(logEntry);
throw error;
}
}
}Querying Anthropic Logs
-- Token usage by model
SELECT
model,
SUM(input_tokens) as total_input,
SUM(output_tokens) as total_output,
COUNT(*) as requests
FROM "anthropic-logs"
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY model
ORDER BY total_input + total_output DESC
-- Latency percentiles
SELECT
model,
AVG(duration_ms) as avg_latency,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY duration_ms) as p50,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_ms) as p95,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY duration_ms) as p99
FROM "anthropic-logs"
WHERE success = true
GROUP BY model
-- Error analysis
SELECT
error_type,
error,
COUNT(*) as count
FROM "anthropic-logs"
WHERE success = false
GROUP BY error_type, error
ORDER BY count DESCBest Practices
- Log Both Providers - Compare OpenAI vs Anthropic
- Track Stop Reasons - Monitor truncations
- Monitor Streaming - Track streaming vs non-streaming
- Cost Tracking - Calculate costs per model
Next Steps
- Configure OpenAI logging
- Set up LangChain tracing
- Create dashboards for LLM metrics
Was this page helpful?