ToolNode execution so an output mock can be returned without running the tool against the real world.
Canonical signatures: TypeScript reference · Python reference
Supported Languages
Working with plain LangChain (no LangGraph)? The same handler serves both, and the SDK exposes it under a LangChain name too:
getLangChainCallbackHandler() / get_langchain_callback_handler() and the BitfabLangChainCallbackHandler class are aliases of their LangGraph counterparts. Use whichever name matches your stack; the behavior is identical.
Quick Start
Mock LangGraph tool calls on replay
Callbacks observe tool execution but cannot prevent it. Use the first-class LangGraph integration when tools need output mocks during replay. It combines:wrapTools()in TypeScript, or LangGraph’s nativeToolNodehooks in Python, at the tool execution boundarycreateInvoker()/create_invoker()for the normal graph entry point; it adds the callback handler and replayable root togethercallbackHandler/callback_handlerandwrapInvoke()/wrap_invoke()as low-level primitives for workflows with meaningful application work around the graph invocation
ToolNode. LangGraph JS does not currently expose equivalent tool-call hooks, so the TypeScript integration wraps each tool’s public Runnable invoke() method and returns a normal tools array for bindTools() and ToolNode. It does not subclass ToolNode or depend on protected internals.
The invoker uses LangGraph’s public withConfig() / with_config() API, so invocation-time configuration and existing callbacks are preserved. Only the graph input is recorded as the replay root input; callback managers and runtime configuration are not serialized. For an async-only Python graph, use create_async_invoker() and replay the returned async callable.
When input preparation or post-processing should be inside the same trace, keep using the low-level form: pass callbackHandler / callback_handler in the graph invocation config and wrap the full application function with wrapInvoke() / wrap_invoke().
By default, every integration-managed tool is marked for replay mocking. Pass mockToolsOnReplay: ["searchDocs"] or mock_tools_on_replay=["search_docs"] to mark only selected tools; pass false to leave them unmarked. Replay’s mock: "all" strategy and matching mock overrides can still substitute any integration-managed tool.
The integration records a serializable view of LangGraph’s result and reconstructs a real ToolMessage or Command during replay. Every reconstructed tool message uses the current invocation’s tool-call ID, so a recorded result remains valid when the model generates a different ID.
Recorded tool calls are matched by tool name and occurrence order within one replay item. If a tool is marked for replay mocking and that occurrence has no recorded output or matching override, Bitfab refuses to execute the live tool. LangGraph then applies the ToolNode’s configured error behavior. Use replay with mock: "none" only when running real tools is intentional.
Current alpha limitations
- Repeated tools are order-sensitive. Calls are matched by tool name and occurrence order. If the same tool runs concurrently, or current code changes its call order, replay can select the wrong recorded occurrence. Use this integration first on graphs whose same-name tool calls have deterministic ordering.
- Every tool call must cross the integration boundary. In TypeScript, use the array returned by
wrapTools()everywhere the graph references those tools. In Python, construct each replayableToolNodewith the integration’s hooks. Calling the original tool directly is not intercepted and cannot be mocked. - TypeScript proxies the public
invoke()method. Standard LangChain tools work through this boundary. Custom tool objects that depend on exact object identity, custom proxy behavior, or a path that bypassesinvoke()may be incompatible. - Tool inputs and outputs must serialize. Native
ToolMessageandCommandresults are supported. Streaming tool results and unusual custom return objects are not yet guaranteed to round-trip. - Fail-closed errors follow
ToolNodeerror handling. When tool-error handling is enabled, LangGraph may turn a missing-recording error into an errorToolMessageand let the graph continue. Configure the node to propagate tool errors if a missing recording should abort the replay. - The convenience invoker covers
invoke()/ainvoke(). For streaming, batch execution, or custom graph entry points, usecallbackHandler/callback_handlerandwrapInvoke()/wrap_invoke()directly.
Plain LangChain Chains
The handler works the same way on LangChain chains and runnables; pass it incallbacks when invoking:
What Gets Captured
The callback handler hooks into LangChain’s callback system and creates spans automatically:Trace Lifecycle
The first callback in a framework invocation becomes the trace root. For full LangGraph runs this is usually a chain/graph node; for plain LangChain usage it can also be a direct chat model, LLM, tool, or retriever call. The handler registers that root immediately as a pending external trace, then completes it when the root callback ends, so long-running invocations can appear as in progress before their final output is available. If the handler runs inside an active BitfabwithSpan / @span root, its spans attach to that outer trace instead. In that case the handler still records the framework span tree, but the outer Bitfab root owns final trace completion.
You do not need an outer withSpan / @span root when the workflow is just the LangGraph or LangChain invocation. Add one only when there is meaningful application work around invoke() that should be part of the same trace, such as input preparation, retrieval outside LangChain, post-processing, persistence, or downstream service calls. When you do add an outer root, use the same trace function key for the outer span and the callback handler.
LangGraph Metadata
LangGraph-specific metadata is automatically extracted and stored as span context:langgraph_step: Current step numberlanggraph_node: Current node namelanggraph_triggers: What triggered this nodelanggraph_path: Execution pathlanggraph_checkpoint_ns: Checkpoint namespace
Token Usage
For LLM spans, token usage is captured from the LLM result. The handler prefers LangChain’s standardizedusage_metadata on each generation’s message (which is also how usage arrives on the final aggregated chunk of streaming runs), then falls back to provider-native response_metadata shapes (OpenAI, Anthropic, Google Gemini / Vertex), and finally the legacy llm_output.token_usage location:
inputTokens: Prompt tokens. For Anthropic, cache reads and cache creation are added back so the value reflects the true prompt size.outputTokens: Completion tokenstotalTokens: Total tokenscachedInputTokens: Cached prompt tokens, fromusage_metadata.input_token_details.cache_read, OpenAIprompt_tokens_details.cached_tokens, or Anthropiccache_read_input_tokensmodel: Model name (extracted from serialized config or metadata)
stream_usage: true / stream_options: {"include_usage": true}), the fields are left unset rather than estimated.
TypeScript
Installation
Method Signature
traceFunctionKey(string, required): Groups all traces from this handler under one key in Bitfab
BitfabLangGraphCallbackHandler that implements the LangChain callback handler interface (duck-typed, no @langchain/core dependency required).
Usage
Callback Hooks
LangChain invokes these callback hooks on the handler:Nesting with Core Tracing
The handler integrates with Bitfab’s span stack. If the application has meaningful work around the graph or chain invocation, create a same-keywithSpan wrapper around that outer workflow and the LangGraph spans nest as children. Handler-only instrumentation is enough for a plain LangGraph / LangChain call:
bitfab.getFunction(...) and bitfab.getLangGraphCallbackHandler(...) take a key; pass the same key ("my-pipeline" above) to both. If you use two different keys here, the same flow will register as two separate overlapping trace functions in the dashboard, an anti-pattern to avoid.
Error Handling
- GraphBubbleUp: LangGraph’s internal interrupt mechanism. Detected automatically and completed silently (no error recorded).
- All other errors: Error message captured in the span. The handler never throws; all callbacks are wrapped in try/catch.
- Missing serialized callback data: Some framework versions can pass nullish serialized payloads. The handler still records the span using fallback names such as
chain,llm,tool, orretriever. - Reusability: The handler resets after each root span completes and can be reused across multiple invocations.
Python
Installation
Method Signature
trace_function_key(str, required): Groups all traces from this handler under one key in Bitfab
BitfabLangGraphCallbackHandler instance that extends BaseCallbackHandler from langchain-core.
Usage
Callback Methods
The handler implements these LangChain callback methods:Nesting with Core Tracing
Bind the key once withget_function so the root and the handler share it (no repeated string to keep in sync). Use this only when the application has meaningful work around the graph or chain invocation; handler-only instrumentation is enough for a plain LangGraph / LangChain call:
get_function does this for you. The plain @bitfab.span("my-pipeline") / bitfab.get_langgraph_callback_handler("my-pipeline") forms work too, but you must pass the same key to both, otherwise the same flow registers as two separate overlapping trace functions in the dashboard, an anti-pattern to avoid.
Error Handling
- GraphBubbleUp: LangGraph’s internal interrupt mechanism. Detected automatically and completed silently (no error recorded).
- All other errors:
repr(error)captured in the span. The handler never raises; all callbacks are wrapped in try/except. - Missing serialized callback data: Some framework versions can pass nullish serialized payloads. The handler still records the span using fallback names such as
chain,llm,tool, orretriever. - Reusability: The handler resets after each root span completes and can be reused across multiple invocations.
Replay
Callback-only graphs are replayable, even though no@span / withSpan root exists in the application code. The handler records the graph invocation as the root span, with the initial graph state as the recorded input. Their observed framework calls run live during replay. To replay, pass the handler’s key plus a plain callable that re-invokes the graph (the SDK wraps it internally):
config["configurable"] values, dependency objects, API keys); the trace records only the graph state. Put unsafe calls behind replay-mockable marked spans; use a no-op only for a replay-only callback slot with no recorded call to mock. For LangGraph tools that must not run, use getLangGraphIntegration() / get_langgraph_integration() as shown above. On SDKs that predate explicit-key replay, wrap the callable under the same key yourself (Python: @bitfab.span("weather-agent") with a (**state) signature; TypeScript: getFunction("weather-agent").withSpan(...)). Full details: Replaying handler-instrumented functions in the Python SDK and TypeScript SDK pages.