Callbacks & Event Hooks
LangChain's callback system hooks into every stage of chain execution — but most teams reach for it when LangSmith, astream_events, or @traceable would serve them better. This article teaches you which mechanism to reach for, how to write production-grade handlers, and how callbacks fail in ways that bring down your whole chain.
Quick Reference
- →Start with LangSmith (2 env vars) — it covers 80% of observability needs with zero code
- →Use astream_events v2 when you need real-time event streaming to a UI or SSE endpoint
- →Use @traceable when you want LangSmith tracing on arbitrary functions outside LangChain Runnables
- →Write BaseCallbackHandler only for custom logic LangSmith can't provide: custom metrics push, cost alerts, external integrations
- →A sync callback that throws crashes the entire chain — always isolate with try/except
- →dispatch_custom_event lets you emit structured progress events from inside any Runnable
- →AsyncCallbackHandler is required in async applications — sync handlers block the event loop and add latency to every call
When NOT to Write a Custom Callback
Before writing a callback handler, check whether one of these three alternatives already covers your need — in order of preference:
start with LangSmith — reach for callbacks only when LangSmith can't do it
| Need | Reach for | Why |
|---|---|---|
| Full traces, latency, token counts | LangSmith (env vars) | Zero code — traces every Runnable automatically |
| Real-time events to a UI or SSE stream | astream_events v2 | No handler class needed — async generator over events |
| Trace arbitrary non-LangChain functions | @traceable decorator | Works on any Python function, not just Runnables |
| Custom metrics push, cost alerts, side effects on every LLM/tool event | BaseCallbackHandler | When you need code to run on every event, every time |
If your goal is to see what happened in a chain — inputs, outputs, latency, token usage, errors — LangSmith gives you all of it with two environment variables. Write a custom handler only when you need to act on events programmatically: push to a metrics system, fire an alert, compute a running cost total.