axonpush
TypeScript SDKIntegrations

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

Identifieraxonpush eventTypePayload
chain.startagent.startchain_type, truncated inputs
chain.endagent.endtruncated outputs
chain.erroragent.errorerror, error_type
llm.startagent.startmodel, prompt_count
llm.endagent.endgenerations (count)
llm.tokenagent.llm.tokentoken
llm.erroragent.errorerror, error_type
tool.{name}.startagent.tool_call.starttool_name, input (first 2000 chars)
tool.endagent.tool_call.endtruncated output
tool.erroragent.errorerror, 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.