Parseable

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 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