LangChain / LangGraph¶
Overview¶
LangChainConnector (src/chatomon/connectors/langchain.py) builds an atom into a LangGraph CompiledStateGraph with langchain.agents.create_agent and installs chatomon's checks as middleware. It needs the langchain extra (pip install 'chatomon[langchain]').
Building the Agent¶
Atom.build() returns the compiled graph; every extra keyword argument of the connector goes to create_agent (tools, checkpointer, middleware, ...). chatomon sets the prompt and name from the atom, so system_prompt, response_format and name are refused with ChatomonConfigError. Your own middleware runs inside chatomon's layer.
Hooks¶
| Hook | What chatomon does there |
|---|---|
before_agent |
Runs the input chain, redacts a blocked message by id, jumps to the end when an orbital replies |
wrap_model_call |
Adds the guidance, runs the output chain and the regenerations; rebuilds a turn resumed by another process or agent instance |
wrap_tool_call |
Closes the turn as aborted when a tool raises |
after_agent |
Finishes the turn, or closes it as aborted when it has no final reply, and clears chatomon's private state |
Every hook has a sync and an async variant, so both invoke and ainvoke are supported.
Conversations¶
The checkpointer keeps the conversation and its thread_id becomes the conversation id of every report; a blocked message is replaced by the placeholder in the latest checkpoint.
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
from chatomon import Atom, TurnReport
from chatomon.connectors.langchain import LangChainConnector
reports: list[TurnReport] = []
model = GenericFakeChatModel(messages=iter([AIMessage(content="Hi!"), AIMessage(content="Bye!")]))
agent = Atom(
name="speedypack",
instructions="Be brief.",
connector=LangChainConnector(model, checkpointer=InMemorySaver()),
on_turn=reports.append,
).build()
config = {"configurable": {"thread_id": "customer-42"}}
agent.invoke({"messages": [HumanMessage(content="hello")]}, config)
agent.invoke({"messages": [HumanMessage(content="thanks")]}, config)
assert [report.conversation_id for report in reports] == ["customer-42", "customer-42"]
Earlier checkpoints keep the original message
A LangGraph checkpointer keeps every earlier snapshot of the thread, and the snapshot written before chatomon runs still contains the original user message. The nucleus never sees it again — replaying a thread runs the input orbitals again — but it remains in storage: pruning and retention of checkpoints are the application's responsibility, as for any other personal data.
Streaming¶
With input orbitals only, tokens stream normally. With output orbitals the model is tagged nostream and checked inside the model call, so the graph's output streams (stream / astream with messages, updates or values) emit only the approved reply.
astream_events and callbacks are not a safe user-facing channel
astream_events and callback handlers observe every internal model call, so when output orbitals exist they also see drafts that the output orbitals later reject. Use them for tracing, not to feed a user interface.
Tracing¶
LangChain does not propagate the OpenTelemetry context through its hooks. LangChainConnector accepts a parent_span callable that returns the span chatomon's spans should nest under; without it they attach to the caller's current span.
Failures and Human in the Loop¶
Failures of the model call and of tools close the turn as aborted (see Failures). A turn interrupted by interrupt() stays open and is closed when the graph resumes; if it is resumed by another process or agent instance, chatomon rebuilds the turn and runs the input orbitals again before the model is called, so the reply is always checked. A thread paused by a graph built without chatomon and resumed on the atom's agent is rebuilt in the same way, and the rebuilt turn lasts until the graph ends, through tool calls and further model calls. If the model call that rebuilds it is interrupted in turn, that turn is closed as aborted and built again when the graph resumes: the exchange leaves two reports. A turn left behind by a failure elsewhere is closed as aborted when the next message arrives on the same thread.
A model reply with no text and no tool call is not checked and leaves the turn open. If the graph ends there, the turn is closed as aborted and the state keeps the empty message; if a middleware of yours sends the graph back to the model, the reply that follows is checked in the same turn.
Related Documentation¶
- Connectors — shared connector behavior
- PydanticAI — the PydanticAI connector
- Microsoft Agent Framework — the MAF connectors
- Frameworks in the Studio — trying a LangChain atom in the local studio
- API Reference:
chatomon.connectors.langchain— every parameter and a runnable example
Last Updated: October 2026 Status: Implemented