Anthropic
Log Anthropic Claude API calls to Parseable
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?