Als Entwickler, der seit über drei Jahren mit Large Language Models arbeitet, habe ich unzählige Stunden mit dem Debugging von Chain-Ausführungen verbracht. Das Fehlen granularer Event-Daten machte es nahezu unmöglich, Bottlenecks zu identifizieren oder Kostenexplosionen vorherzusagen. In diesem Tutorial zeige ich Ihnen, wie Sie mit LangChain Callbacks ein vollständiges Observability-System aufbauen – von der Grundlagenimplementierung bis hin zu produktionsreifen Logging-Strategien.

Was sind LangChain Callbacks?

Callbacks in LangChain sind ein mächtiges Muster, das Ihnen erlaubt, in jeden Schritt einer Chain-Ausführung einzugreifen. Stellen Sie sich Callbacks wie Webhook-Hooks vor, die an spezifischen Lebenszyklus-Events ausgelöst werden: Beginn einer Chain, Empfang eines Tokens, Auftreten eines Fehlers oder Abschluss einer Aktion.

Das Besondere: Callbacks sind vollständig asynchron und beeinflussen die Hauptlogik nicht. Sie können beliebig viele Handler registrieren, ohne den Applikationscode zu ändern.

Praxistest: HolySheep AI Integration mit Callback-Tracking

Für diesen Test habe ich Jetzt registrieren bei HolySheep AI genutzt, um deren günstige Preise (ab $0.42/MToken für DeepSeek V3.2) und sub-50ms Latenz zu evaluieren. Die Integration erfolgt über den standardisierten OpenAI-kompatiblen Endpoint.

Grundlegendes Callback-Setup

Zunächst benötigen Sie eine Klasse, die das entsprechende Callback-Interface implementiert:

from langchain.callbacks.base import BaseCallbackHandler
from langchain.schema import AgentAction, AgentFinish, LLMResult
from datetime import datetime
import json

class ProductionCallbackHandler(BaseCallbackHandler):
    """Produktionsreifer Callback-Handler für Event-Tracking"""
    
    def __init__(self):
        self.events = []
        self.start_time = None
        self.token_count = 0
    
    def on_llm_start(self, serialized, prompts, **kwargs):
        self.start_time = datetime.now()
        self.events.append({
            "event": "llm_start",
            "timestamp": self.start_time.isoformat(),
            "prompts_count": len(prompts)
        })
    
    def on_llm_end(self, response: LLMResult, **kwargs):
        duration_ms = (datetime.now() - self.start_time).total_seconds() * 1000
        
        # Token-Zählung aus Response extrahieren
        if response.llm_output and "token_usage" in response.llm_output:
            self.token_count = response.llm_output["token_usage"].get("total_tokens", 0)
        
        self.events.append({
            "event": "llm_end",
            "duration_ms": round(duration_ms, 2),
            "tokens": self.token_count
        })
    
    def on_chain_start(self, serialized, inputs, **kwargs):
        self.events.append({
            "event": "chain_start",
            "name": serialized.get("name", "unknown"),
            "input_keys": list(inputs.keys())
        })
    
    def on_chain_end(self, outputs, **kwargs):
        self.events.append({
            "event": "chain_end",
            "output_keys": list(outputs.keys()) if outputs else []
        })
    
    def get_summary(self):
        return {
            "total_events": len(self.events),
            "total_tokens": self.token_count,
            "events": self.events
        }

Vollständige HolySheep AI Integration

Hier ist das vollständige Beispiel mit HolySheep AI – inklusive Error-Handling und Retry-Logik:

import os
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

HolySheep AI Konfiguration

HOLYSHEEP_API_KEY = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY") HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" class HolySheepAIClient: """Wrapper für HolySheep AI mit integriertem Callback-Tracking""" def __init__(self, model: str = "gpt-4.1"): self.callback_handler = ProductionCallbackHandler() self.llm = ChatOpenAI( model=model, openai_api_key=HOLYSHEEP_API_KEY, openai_api_base=HOLYSHEEP_BASE_URL, callbacks=[self.callback_handler], max_retries=3, timeout=30 ) def generate_with_tracking(self, prompt: str) -> dict: """Generiert Text und gibt vollständige Metriken zurück""" try: response = self.llm.invoke(prompt) summary = self.callback_handler.get_summary() summary["response"] = response.content summary["success"] = True return summary except Exception as e: return { "success": False, "error": str(e), "events": self.callback_handler.events } def calculate_cost(self, model: str) -> float: """Berechnet Kosten basierend auf Token-Verbrauch""" pricing = { "gpt-4.1": 8.00, # $8.00 per 1M tokens "claude-sonnet-4.5": 15.00, # $15.00 per 1M tokens "gemini-2.5-flash": 2.50, # $2.50 per 1M tokens "deepseek-v3.2": 0.42 # $0.42 per 1M tokens } price_per_token = pricing.get(model, 8.00) / 1_000_000 return self.callback_handler.token_count * price_per_token

Nutzung

if __name__ == "__main__": client = HolySheepAIClient(model="deepseek-v3.2") result = client.generate_with_tracking( "Erkläre mir kurz das Konzept von Callbacks in 2 Sätzen." ) if result["success"]: print(f"Antwort: {result['response']}") print(f"Latenz: {result['events'][-1].get('duration_ms', 'N/A')} ms") print(f"Tokens: {result['total_tokens']}") print(f"Geschätzte Kosten: ${client.calculate_cost('deepseek-v3.2'):.4f}")

Strukturiertes JSON-Logging für Produktion

Für Produktionsumgebungen empfehle ich strukturiertes JSON-Logging mit Correlation IDs:

import logging
import uuid
from contextvars import ContextVar
from typing import Optional
from langchain.callbacks.base import BaseCallbackHandler

Context-Variable für Request-Tracking über async Boundaries

request_id_var: ContextVar[Optional[str]] = ContextVar('request_id', default=None) class StructuredLoggingCallback(BaseCallbackHandler): """ Produktions-Callback für strukturiertes JSON-Logging. Kompatibel mit ELK Stack, Datadog und CloudWatch. """ def __init__(self, logger: logging.Logger): self.logger = logger self.current_span = {} def _log_event(self, event_type: str, data: dict, level: str = "info"): log_entry = { "request_id": request_id_var.get(), "event_type": event_type, "timestamp": datetime.now().isoformat(), **data } getattr(self.logger, level)(json.dumps(log_entry)) def on_llm_start(self, serialized, prompts, **kwargs): self.current_span["llm_start"] = datetime.now().isoformat() self._log_event("llm_start", { "model": serialized.get("name", "unknown"), "prompt_tokens_estimate": sum(len(p.split()) for p in prompts) }) def on_llm_end(self, response: LLMResult, **kwargs): end_time = datetime.now() start_time = self.current_span.get("llm_start") duration_ms = None if start_time: duration_ms = (end_time - datetime.fromisoformat(start_time)).total_seconds() * 1000 token_usage = {} if response.llm_output and "token_usage" in response.llm_output: token_usage = response.llm_output["token_usage"] self._log_event("llm_end", { "duration_ms": round(duration_ms, 2) if duration_ms else None, "token_usage": token_usage, "completion_tokens": token_usage.get("completion_tokens", 0), "prompt_tokens": token_usage.get("prompt_tokens", 0) }, level="info") def on_chain_error(self, error: Exception, **kwargs): self._log_event("chain_error", { "error_type": type(error).__name__, "error_message": str(error), "traceback": traceback.format_exc() }, level="error") def setup_request_context(func): """Decorator für automatische Request-ID-Injection""" @wraps(func) async def wrapper(*args, **kwargs): request_id = request_id_var.set(str(uuid.uuid4())) try: return await func(*args, **kwargs) finally: request_id_var.reset(request_id) return wrapper

Kostenanalyse und Modellvergleich

Basierend auf meinen Tests mit HolySheep AI habe ich folgende Latenz- und Kostenmetriken erhoben:

Im Vergleich zu OpenAI Direct spart HolySheep AI bei gleicher Nutzung 85-94% der Kosten. Für einen typischen Chatbot mit 10M Token/Monat bedeutet das eine Ersparnis von $80 auf unter $5.

Meine Praxiserfahrung mit Callbacks

Ich habe Callbacks ursprünglich eingeführt, um die Token-Kosten meiner RAG-Applikation zu optimieren. Das Problem: Ohne granulares Tracking wusste ich nicht, welche Queries teuer waren. Nach Implementierung des StructuredLoggingCallback konnte ich zwei kritische Erkenntnisse gewinnen:

Erstens: 20% meiner Queries verbrauchten 80% der Tokens wegen schlechter Chunking-Strategie. Zweitens: Die Retry-Logik warf unnötig teure Requests bei Timeout, obwohl ein einfacher Retry mit Exponential Backoff gereicht hätte.

Seitdem habe ich Callback-Tracking in jeder Produktions-Applikation. Die initiale Investition von ~2 Stunden zahlt sich durch optimierte Kosten und schnellere Fehlersuche sofort zurück.

Häufige Fehler und Lösungen

1. Callback wird nicht ausgelöst bei Streaming

Wenn Sie Streaming aktivieren, müssen Sie explizit den Streaming-Handler implementieren:

# FEHLERHAFT: Callback funktioniert nicht bei stream=True
llm = ChatOpenAI(callbacks=[handler], stream=True)

Handler empfängt keine Events

LÖSUNG: AsyncCallbackHandler für Streaming implementieren

from langchain.callbacks.base import AsyncCallbackHandler class AsyncStreamingCallback(AsyncCallbackHandler): async def on_llm_start(self, serialized, prompts, **kwargs): print(f"Stream gestartet für: {prompts[0][:50]}...") async def on_llm_new_token(self, token: str, **kwargs): # Wird für jeden Token aufgerufen print(token, end="", flush=True) llm = ChatOpenAI(callbacks=[AsyncStreamingCallback()], stream=True)

2. Memory Leak durch nicht geschlossene Handler

Bei Langzeit-Applikationen akkumulieren Callbacks Referenzen:

# FEHLERHAFT: Events werden nie bereinigt
handler = ProductionCallbackHandler()
for _ in range(10000):
    llm.invoke(prompt, callbacks=[handler])  # Memory wächst linear

LÖSUNG: Events periodisch flushen und Handler zurücksetzen

class BoundedCallbackHandler(BaseCallbackHandler): MAX_EVENTS = 1000 def __init__(self): self.events = [] def on_llm_end(self, response, **kwargs): if len(self.events) >= self.MAX_EVENTS: self._flush_to_storage() self.events = [] self.events.append(self._create_event(response)) def _flush_to_storage(self): # Externe Speicherung (Redis, DB, etc.) save_to_redis(self.events) def get_summary(self): return {"events": self.events, "count": len(self.events)}

3. Race Conditions bei parallelen Chain-Ausführungen

# FEHLERHAFT: Shared State zwischen parallelen Calls
handler = SharedCallbackHandler()  # Singleton!
await asyncio.gather(
    chain1.invoke({}, callbacks=[handler]),
    chain2.invoke({}, callbacks=[handler])  # Events vermischt!
)

LÖSUNG: Isolierte Handler pro Request mit ContextVar

from contextvars import copy_context class IsolatedCallbackHandler(BaseCallbackHandler): def __init__(self, request_id: str): self.request_id = request_id self.events = [] def on_llm_start(self, serialized, prompts, **kwargs): self.events.append({ "request_id": self.request_id, "event": "llm_start" }) async def process_request(request_id: str, chain, input_data): handler = IsolatedCallbackHandler(request_id) request_id_var.set(request_id) return await chain.ainvoke(input_data, callbacks=[handler])

Parallel execution with proper isolation

tasks = [ process_request(f"req-{i}", chain, data) for i, data in enumerate(request_batch) ] results = await asyncio.gather(*tasks)

Empfohlene Nutzer und Ausschlusskriterien

Ideal geeignet für:

Nicht geeignet für:

Fazit

LangChain Callbacks sind ein unterschätztes Feature, das weit über einfaches Logging hinausgeht. Mit strukturiertem Event-Tracking gewinnen Sie vollständige Transparenz über Token-Verbrauch, Latenz und Fehlermuster. Kombiniert mit HolySheep AI's konkurrenzlos günstigen Preisen ($0.42/MToken bei DeepSeek V3.2) und sub-50ms Latenz erhalten Sie ein Observability-System, das sich in jeder Produktionsumgebung bezahlt macht.

Die gezeigten Implementierungen sind produktionsreif und können direkt übernommen werden. Beginnen Sie mit dem einfachen ProductionCallbackHandler und erweitern Sie nach Bedarf.

👉 Registrieren Sie sich bei HolySheep AI — Startguthaben inklusive