LangChain
A callback handler that traces chain, LLM and tool lifecycle events from LangChain and LangGraph.
pip install "axonpush[langchain]"Tested against langchain-core>=0.1,<0.4, including the 0.2 kwargs-only
callback reshape.
Attach the handler
from axonpush import AxonPush
from axonpush.integrations.langchain import AxonPushCallbackHandler
client = AxonPush()
handler = AxonPushCallbackHandler(client, channel_id, agent_id="research-agent")
result = chain.invoke(
{"input": "What are AI agents?"},
config={"callbacks": [handler]},
)
handler.flush(timeout=2.0)client and channel_id are positional. If you would rather not pick the
class yourself, get_langchain_handler(client, channel_id, **kwargs) returns
the async handler for an AsyncAxonPush and the sync one otherwise.
Match the handler to the client. AsyncAxonPushCallbackHandler needs an
AsyncAxonPush; the sync handler needs an AxonPush. A mismatch does not
raise, events are dropped with a warning on the axonpush logger.
Constructor
AxonPushCallbackHandler(
client, # AxonPush - positional
channel_id, # str UUID - positional
*,
agent_id="langchain",
trace_id=None,
metadata=None, # merged into every event's metadata
mode=None, # "background" (default) | "sync"
queue_size=1000,
shutdown_timeout=2.0,
)AsyncAxonPushCallbackHandler is the same, except it takes max_pending=1000
in place of queue_size / shutdown_timeout.
trace_id pins every event this handler emits to one trace; omit it and the
handler adopts the trace active on the current context, creating one if
needed.
What gets published
| Identifier | Event type | Payload |
|---|---|---|
chain.start | agent.start | chain_type, inputs |
chain.end | agent.end | outputs |
chain.error | agent.error | error, error_type |
llm.start | agent.start | model, prompt_count |
llm.end | agent.end | generations |
llm.token | agent.llm.token | token |
tool.<name>.start | agent.tool_call.start | tool_name, input (first 2000 chars) |
tool.end | agent.tool_call.end | output |
tool.error | agent.error | error, error_type |
Every event carries framework: "langchain" in metadata.
Trace shape
LangChain’s run_id becomes the event’s span_id, and parent_run_id
becomes parent_event_id, so nested chains and sub-chains land as a connected
tree rather than as siblings. Both ids are also mirrored into metadata as
langchain_run_id and langchain_parent_run_id.
LangGraph
LangGraph compiles nodes into anonymous Runnables, so serialized arrives
empty and the node identity is in the callback kwargs. The handler resolves a
name from kwargs["name"], then metadata["langgraph_node"], then
serialized["name"], then the last segment of serialized["id"], falling back
to "Runnable".
It also promotes the framework’s own context into event metadata, with no configuration:
langgraph_node,langgraph_step,langgraph_triggersthread_idrun_typetags
so you can group and filter a graph run by node in the dashboard.
Before v0.0.13 every LangGraph step landed as chain_type: "unknown". If you
have dashboards filtering on that literal, retire them.
Model names
llm.start reports the configured model id, not the LangChain wrapper class.
It reads invocation_params["model"] or ["model_name"] first (set at
call-time by every modern Chat* integration), then
serialized["kwargs"]["model"], and only then falls back to the class name.
So you get gpt-4o-mini, not ChatOpenAI.
Publishing modes
mode picks how events leave the process.
| Mode | Backend | When |
|---|---|---|
"background" (default) | Bounded in-process queue drained by a worker | Almost always. Callbacks stay O(microseconds). |
"sync" | Direct HTTP call on the callback thread | Tests and debugging. Not supported on the async handler, which drops events in this mode. |
The background queue holds 1000 events by default and drops the oldest when
full, with a rate-limited warning on the axonpush.publisher logger.
Flush before you exit
Background publishing means an event can still be queued when your process, or your serverless container, stops.
handler.flush(timeout=2.0) # block until drained, or give up
handler.close() # drain and stop the workerOn the async handler these are await handler.aflush(...) and
await handler.aclose().
Integrations
Drop-in handlers that trace the agent frameworks and logging libraries you already use, LangChain, OpenAI Agents, Anthropic, CrewAI, Deep Agents, plus stdlib logging, Loguru, structlog, print() and OpenTelemetry.
OpenAI Agents
Lifecycle hooks that trace agent runs, tool calls and handoffs from the OpenAI Agents SDK.