LangChain
Attach AxonPushCallbackHandler to a LangChain.js runnable and every chain, LLM, and tool lifecycle event ships to axonpush.
AxonPushCallbackHandler implements LangChain.js’s callback interface. Pass it
anywhere LangChain accepts callbacks and the chain, LLM, and tool lifecycles
are recorded without touching your chain code.
Tested against langchain@^0.3 and @langchain/core@^0.3.
Install
npm install @axonpush/sdk @langchain/core
Attach the handler
import { AxonPush, AxonPushCallbackHandler } from "@axonpush/sdk";
const client = new AxonPush();
const handler = new AxonPushCallbackHandler({
client,
channelId: process.env.AXONPUSH_CHANNEL_ID!,
agentId: "langchain-demo",
});
The sub-path import works too, and tree-shakes better:
import { AxonPushCallbackHandler } from "@axonpush/sdk/integrations/langchain";
Then hand it to any runnable, per invocation or at construction:
import { ChatOpenAI } from "@langchain/openai";
import { RunnableSequence } from "@langchain/core/runnables";
const chain = RunnableSequence.from([prompt, new ChatOpenAI({ model: "gpt-4o" })]);
const result = await chain.invoke(input, { callbacks: [handler] });
agentId defaults to "langchain" when you omit it. Every event carries
metadata.framework: "langchain".
What gets emitted
| Identifier | axonpush eventType | Payload |
|---|---|---|
chain.start | agent.start | chain_type, truncated inputs |
chain.end | agent.end | truncated outputs |
chain.error | agent.error | error, error_type |
llm.start | agent.start | model, prompt_count |
llm.end | agent.end | generations (count) |
llm.token | agent.llm.token | token |
llm.error | agent.error | error, error_type |
tool.{name}.start | agent.tool_call.start | tool_name, input (first 2000 chars) |
tool.end | agent.tool_call.end | truncated output |
tool.error | agent.error | error, error_type |
Inputs and outputs are JSON-serialised and truncated at 2000 characters before publishing, then run through the client’s redactor.
Run identity
Two derivations matter for how traces read in the UI, and both changed in
0.0.7:
chain_type resolves in order: the explicit runName LangChain passes,
then metadata.langgraph_node, then serialized.name, then the last segment
of serialized.id, then the literal "Runnable". Before 0.0.7 the handler
read only serialized.name, which LangGraph never sets, every graph step
showed up as "unknown".
model resolves in order:
extraParams.invocation_params.{model,model_name,model_id}, then
extraParams.{model,…}, then serialized.kwargs.{model,…}, then
serialized.name, then "unknown". Modern Chat* wrappers put the configured
model in invocation_params at call time, so this now records
gpt-4o rather than the wrapper class name ChatOpenAI.
Trace correlation
LangChain’s runId and parentRunId are attached to every event as
metadata.langchain_run_id and metadata.langchain_parent_run_id, so the
runtree is reconstructible in the UI.
handleChainStart and handleLLMStart also lift LangChain’s trailing
positional args into metadata when present: langgraph_node, langgraph_step,
langgraph_triggers, thread_id, run_type, and tags. You can group and
filter on those without configuring anything at handler construction.
To join a LangChain run to a trace you started elsewhere, seed it:
const handler = new AxonPushCallbackHandler({
client,
channelId,
traceId: existingTrace.traceId,
});
Failure behaviour
Publishing is fire-and-forget. Each callback publishes without awaiting, and a
failed publish is logged at warning level and swallowed, an axonpush outage
cannot fail your chain. Combined with the client’s failOpen default, nothing
in this integration throws into LangChain.
Integrations
Drop-in handlers for the agent frameworks, logging libraries and tracing stacks you already use, LangChain, LangGraph, OpenAI Agents, Anthropic, Vercel AI SDK, LlamaIndex, Mastra, Google ADK, plus pino, winston, console capture, OpenTelemetry, Sentry and BullMQ.
LangGraph
AxonPushLangGraphHandler records every node traversal of a StateGraph on top of the full LangChain callback surface.