Parseable

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 DESC

Best Practices

  1. Log Both Providers - Compare OpenAI vs Anthropic
  2. Track Stop Reasons - Monitor truncations
  3. Monitor Streaming - Track streaming vs non-streaming
  4. Cost Tracking - Calculate costs per model

Next Steps

Was this page helpful?

On this page