Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

callback
A function the library calls at fixed points in a run, such as when a step starts or ends.
chain
A series of steps where each one passes its result to the next, ending in the answer.
invoke
The method that runs a step once with one input and returns its result.
memory
Stored history from earlier turns, added back to a request so the model can refer to what was said.
message
One labelled turn in a chat request, marked as coming from the system, the user or the model.
model
The trained program that reads a message list and returns the next turn.
output parser
The step that turns a model's raw text into a value your program can use, or raises an error.
pipe
The operator that joins two steps into one new step, running the left side first.
prompt
The text a model receives for one request, built from fixed wording and the values filled into it.
prompt template
A reusable prompt with named blanks that are filled in for each request.
runnable
Any step with a standard way to run it, so steps can be joined without knowing each other's code.
streaming
Sending a reply in pieces as it is produced, rather than waiting for the whole text.
tool
A function exposed to a model, with a name, described inputs and a result.
tool call
A model's request to run a named tool with specific arguments, which your code may accept or refuse.

LangChain from the ground up

Callbacks and tracing

A chain that cannot be observed cannot be debugged. Callbacks announce each run, and a trace turns those announcements into a tree with timings, tokens and errors.

Part 8 of 10

Intermediate15 min readBack to Courses

The chain is a black box

A chain is a few calls in a row, and when the answer is wrong the useful question is which call produced the bad input. Print statements answer it for one run and then become noise. Callbacks are the same idea with a seam: the framework announces that a step started, ended or failed, and your object decides what to do with the announcement. The alternative is a debugger that only works on your machine, for the one request you can reproduce.

The announcement carries the run id, the parent run id, the inputs and the output. Those four fields are enough to rebuild a tree, which is what a trace is. Without the parent id you have a list of events; with it you have the shape of the execution, and the shape is what tells you where the time went and which step was retried. A trace without timings tells you the order; with timings it tells you the cost.

A handler is an object with hooks

In LangChain a handler subclasses BaseCallbackHandler and overrides methods such as on_llm_start, on_llm_end, on_tool_start, on_tool_error and on_chain_end. The base class supplies empty implementations, so a handler implements only the hooks it cares about. The same object can count tokens, time calls and report failures without touching the chain itself. Because the base class is permissive, a handler can start as three lines and grow only when a question needs an answer.

Two rules keep handlers harmless. They must be cheap, because they run inline with the request, and they must not raise, because an exception in a handler can fail the call it was observing. Send the event to a queue and let another process write it, and wrap the body in a try/except that records the handler’s own failure and moves on. A handler that writes to a database on every token is the classic way to make a fast chain slow.

A handler that counts tokens

from langchain_core.callbacks import BaseCallbackHandler


class TokenCounter(BaseCallbackHandler):
    def on_llm_end(self, response, **kwargs):
        usage = (response.llm_output or {}).get("token_usage", {})
        print("tokens:", usage.get("total_tokens", "unknown"))

    def on_tool_error(self, error, **kwargs):
        print("tool failed:", type(error).__name__)


chain.invoke({"topic": "retries"}, config={"callbacks": [TokenCounter()]})

Instrument a chain with a handler

Run the pipeline, then add a step that raises. Notice that the error hook fires before the exception escapes, so the trace records the failure as well as the crash.

Output
Click "Run" to execute your code

What to record

A useful trace records more than text. For each run: the name, the parent, the start and end time, the model and prompt version, the token usage, the status and any error. Inputs and outputs are valuable for debugging and dangerous for privacy, so redact them before they leave the process, not after they are stored. The run id is what ties a log line, a metric and a user report to the same execution.

Traces, metrics and logs answer different questions. A trace explains one request; a metric says how often something happens; a log records a discrete event for search. Sending everything to one store makes all three worse, and it makes the bill larger without making the answer clearer. Pick the question first, then the store. A trace that is never queried is a cost with no reader, so decide who will look and what they will ask.

Attach the handler in the config, not inside a step. Nested runs inherit it, so one config instruments the whole tree instead of one call.

Tracing in production

In production, sample. Tracing every request is affordable for a small service and expensive for a large one, and the thousandth identical success teaches nothing. Keep every error, keep a small random sample of successes, and keep a trace id in the response so a user report can be matched to a run without asking the user for a timestamp. The trace id should be opaque, because it is also a handle into your system.

Redaction is part of the design, not a later pass. Email addresses, card numbers and document text should be removed or hashed before the trace is stored, and the rule belongs next to the handler so it cannot be forgotten when a new field is added. A trace store is a second copy of your data, with its own retention and access rules. Retention is part of the same decision: a store that keeps everything forever is a liability with a search box.

Rebuild a tree from flat events

Run the renderer, then add an event whose parent is missing. Decide whether the renderer should drop it, attach it to the root, or report it as a broken trace.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is a callback handler for?