Skip to content
Pyrula

LangChain and LangGraph

from_langchain_tool wraps a LangChain BaseTool as a Pyrula @tool. Hand the result to an agent like any native tool.

from pyrula.agents.integrations import from_langchain_tool
from pyrula.agents.decorators.agent import agent
search = from_langchain_tool(my_langchain_tool, timeout=30.0, retry=0)
@agent
async def assistant(ctx, message: str) -> None:
async for _ in ctx.llm.stream(messages=[{"role": "user", "content": message}], tools=[search]):
...

The tool’s input schema comes from its args_schema when it has one, so the model sees the same parameters. Tools without a schema fall back to a single input: str. Needs pip install pyrula-agents[langchain].

langgraph_agent wraps a compiled graph as a Pyrula @agent. The graph runs inside the turn; Pyrula owns the lifecycle and forwards graph updates into the run stream.

from pyrula.agents.integrations import langgraph_agent
graph_agent = langgraph_agent(my_graph, name="planner", stream_mode="updates")

The wrapped agent takes state and an optional config. When stream=True (the default) it iterates graph.astream(...) and emits each chunk as framework:event, returning the last chunk as the result. Without streaming it falls back to ainvoke or invoke. Needs pip install langgraph.

The wrapped agent registers like any other, so create_app and PyrulaWorker pick it up.

This direction is transport, not durability: the graph re-runs on replay. To get the other direction, see as_langgraph_node below.

as_langgraph_node goes the opposite way: it drops a durable Pyrula @workflow or @agent into a graph you already run, as one node. That node’s body is a Pyrula run, so it is journaled, replayable, and crash-safe, and it can pause for a human.

from pyrula.agents.integrations import as_langgraph_node
node = as_langgraph_node("provision_infra", store=store)
graph.add_node("provision", node)

The run id is derived from the graph’s thread_id, so resuming the graph re-attaches the same run rather than starting a new one, and a crash mid-step won’t double-execute. When the workflow calls ctx.interrupt, the node bridges to LangGraph’s native interrupt() / Command(resume=...): the graph pauses and checkpoints, and resuming it feeds the answer back into the Pyrula run. Human-in-the-loop is durable end to end.

It needs a checkpointed graph (for the thread_id and resume) and a Pyrula worker consuming the store, since the node submits and awaits the run rather than running it inline. Install with pip install pyrula-agents[langgraph]. For the fuller picture of which integrations are durable, see What’s durable, and what isn’t.