BaseCallbackHandler is LangChain's lifecycle-event bus; every tracing vendor implements it to turn chain, LLM, tool, and agent events into spans.
Imagine a kitchen with a clipboard hung next to every station. Every time a chef starts a dish, finishes it, or burns it, they jot a note on the clipboard. At the end of service, anyone who can read those notes can reconstruct exactly what happened: who took how long, what failed, what ran in parallel. LangChain's callback handler is that clipboard. Each subsystem (the LLM, the tool, the chain, the agent) writes start, end, and error notes. Observability vendors are just different people reading the same clipboard and drawing their own picture of the night.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Observability for LLM apps is harder than observability for ordinary microservices because the interesting structure of an LLM call lives in nested abstractions: a chain that calls an agent that calls a tool that calls another LLM. A flat log line cannot represent that tree. You need spans, parent-child relationships, and structured payloads at every boundary.
LangChain's answer to that problem is BaseCallbackHandler. It is the subscription surface the runtime dispatches every lifecycle event into, and it is, not coincidentally, shaped exactly like a tracing-span lifecycle. Understanding the handler surface is the difference between treating LangChain observability as a logging exercise (it is not) and treating it as a tree of spans exercise (it is).
This deep dive walks the surface, the dispatch model, the async and streaming variants, the vendor-integration pattern, and the trade-off between the handler path and the OpenTelemetry path that frames the modern observability vendor split.
The event surface, subsystem by subsystem
LangChain's runtime emits events at five subsystem boundaries, each with a paired start, end, and error event.
LLM calls emit start, end, and error events carrying the serialized model config, prompts, response, and a run_id/parent_run_id pair. Streaming adds on_llm_new_token per emitted token.
Chains emit the start/end/error trio with inputs and outputs. Every LCEL Runnable participates in this surface; a prompt | llm | parser pipe emits chain events for the composite plus LLM events for the inner step.
Tools emit start, end, and error events carrying the tool name, input string, and output. Tools called via @tool or StructuredTool participate automatically.
Agents emit on_agent_action once per tool-selection step and on_agent_finish when the agent loop terminates. These are agent-loop semantics, not span lifecycle, so a tracing handler typically pairs each action with the corresponding tool-start span.
Retrievers emit on_retriever_start(serialized, query, ...), on_retriever_end(documents, ...), and on_retriever_error(error, ...). This was a later addition specifically so RAG pipelines could be traced without custom instrumentation.
The run_id and parent_run_id on every event are what let the handler reconstruct the call tree. A vendor that drops those IDs ends up with a flat list of events and no parent context.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- LangSmith (LangChain Inc.) is implemented as a BaseCallbackHandler that batches events to its hosted backend; it is the reference integration.
- Langfuse ships both a callback handler and an OTel GenAI exporter, so teams that may eject from LangChain still keep their traces.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow does the async callback handler differ from the sync one in modern LangChain?
AsyncCallbackHandler exposes coroutine versions of the same methods (on_llm_start -> aon_llm_start). Async chains expect the async variant; mixing sync handlers into async chains works via thread offload but can deadlock under load.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Thinking the handler is a logging utility you call yourself. It is a subscription surface; you register it once and the framework dispatches every lifecycle event to it automatically.
60 second bullets to scan on the way to the call.
The five subsystems the handler observes
The start, end, error triple per subsystem
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.