Skip to content

nodestep.core.builtin_nodes

Built-in nodes for chat agents and sub-graphs.

model_node calls a chat model and tool_runner runs the tool calls it returns; build_react_agent joins the two in a loop and subgraph runs a graph as a node. See Agents and tools.

Agents

build_react_agent

build_react_agent(chat, *, tools, name='agent', system_prompt=None, middleware=(), tool_errors='raise', stream=False, output_schema=None, max_steps=25, max_tool_calls=50, workspace=None, state_store=None) -> Graph[AgentState]

Build a tool-calling agent graph.

The model node think and the tool node act alternate until the model answers without tool calls. Both nodes get tools followed by the tools() of each middleware in middleware.

Parameters
chatChat

Chat model to call.

toolsSequence[Tool]

Tools the model may call; may be empty.

namestr

Graph name.

system_promptstr | None

System message prepended to every model call.

middlewareSequence[Middleware]

Extra middleware, after the built-in ToolLimitMiddleware.

tool_errorsToolErrorPolicy

What a failed tool call does, as in tool_runner.

streambool

Stream model calls, as in model_node.

output_schematype[BaseModel] | None

Model the final answer is parsed into; final_output is typed output_schema | None. An invalid answer raises StructuredOutputError.

max_stepsint

Superstep limit per call.

max_tool_callsint

Tool-call budget for one run and its resumes.

workspaceWorkspace | None

Workspace exposed to tools.

state_storeStateStore | None

Store that enables threads, interrupts and resume.

Raises
GraphConfigError

If two different tools share a name, or the same tool is listed twice, also through a middleware.

run_agent

async run_agent(graph, message, **kwargs) -> str

Send one user message to an agent and return its final text.

Parameters
graphGraph

Agent graph, usually from build_react_agent.

messagestr

User message.

**kwargsAny

Passed to Graph.ainvoke, e.g. thread_id.

Returns
str

The final text, or "" when there is none, as after a refusal (see AIMessage.refusal).

Raises
RunInterruptedError

If the run paused for human input.

AgentState

AgentState(*, messages=[], tool_calls=[], final_text=None, final_output=None)

State of the built-in ReAct agent.

Subclass of BaseState.

Attributes
messagesAnnotated[list[Message], add_messages]

The conversation; updates are merged with add_messages.

tool_callslist[ToolCall]

Calls of the last model turn that have not run yet.

final_textstr | None

Answer text of the last model turn; None while the model calls tools.

final_outputAny

The answer parsed into output_schema, when the agent has one.

Model and tool nodes

model_node

model_node(name, *, chat, system_prompt=None, tools=(), tool_choice=None, stream=False, repair_history=False, output_schema=None, on_invalid='raise', messages_field='messages', tool_calls_field='tool_calls', final_text_field='final_text', final_output_field='final_output') -> Node

Create a node that calls a chat model.

The node sends the messages of messages_field and appends the model's AIMessage to it. It writes the requested calls to tool_calls_field and the text to final_text_field, or None when the model calls tools or refuses. The before_model and after_model hooks run around the call.

Parameters
namestr

Node name.

chatChat

Chat model to call.

system_promptstr | None

System message prepended to the request.

toolsSequence[Tool]

The node's complete tool list; middleware tools are not added.

tool_choiceLiteral['auto', 'required', 'none'] | str | None

The request's tool choice; another string names the tool to call. None sets none.

streambool

Call chat.stream instead of chat.complete; the chunks become "tokens" events.

repair_historybool

Send a repaired copy of an inconsistent history instead of raising. Tool results that answer no call of the AIMessage before them are dropped; calls without a result get "Tool call was not executed.". The state is not changed.

output_schematype[BaseModel] | None

Model the final answer is parsed into and stored in final_output_field; None on turns that call tools or refuse.

on_invalidLiteral['raise', 'emit']

For an answer that does not validate: raise StructuredOutputError, or emit a "custom" event with a StructuredOutputEvent and store None.

messages_fieldstr

State field of the conversation; it must use add_messages.

tool_calls_fieldstr

State field the requested tool calls are written to.

final_text_fieldstr

State field the answer text is written to.

final_output_fieldstr

State field the parsed answer is written to; used only with output_schema.

Raises
GraphConfigError

If two different tools share a name, or the same tool is listed twice.

TypeError

If chat has no model string.

ValueError

If on_invalid is neither "raise" nor "emit".

GraphConfigError

When the node runs, before the model is called: messages_field does not use add_messages, or a field it writes is not declared.

ChatHistoryError

When the node runs, before the model is called, with repair_history off: a tool call has no result right after it, or a tool result answers no call of the AIMessage before it.

TypeError

When the node runs: a model hook replaced the request or the response with another type.

StructuredOutputEvent

StructuredOutputEvent(*, kind='structured_output_error', schema_name, message, raw_content=None)

Payload of the custom event sent by model_node(on_invalid="emit").

Attributes
kindLiteral['structured_output_error']
schema_namestr

Name of the output_schema model.

messagestr

The validation error.

raw_contentstr | None

The answer text that did not validate.

tool_runner

tool_runner(name, *, tools, tool_errors='raise', messages_field='messages', tool_calls_field='tool_calls') -> Node

Create a node that runs the pending tool calls.

The calls in tool_calls_field run concurrently. Their ToolMessage results are appended to messages_field, and tool_calls_field is cleared. When before_tool middleware changed the arguments, the ToolMessage records the ones the tool ran with in arguments. A call denied with ToolDeniedError always becomes a ToolMessage with the reason. Calls that finished before a pause do not run again on resume.

Parameters
namestr

Node name.

toolsSequence[Tool]

The node's complete tool list; middleware tools are not added.

tool_errorsToolErrorPolicy

What a failed call (an unknown tool, invalid arguments or an exception from the tool) does. "raise" raises it, several as ToolGroupExecutionError. "return" sends "Tool error: <type>: <message>" to the model as the result. A callable turns the exception into that text. ContextNotProvidedError and GraphConfigError from a tool, and an invalid call without a string id, always raise.

messages_fieldstr

State field the ToolMessage results are appended to; it must use add_messages.

tool_calls_fieldstr

State field the calls are read from; the node clears it.

Raises
GraphConfigError

If two different tools share a name, or the same tool is listed twice.

ValueError

If tool_errors is neither "raise", "return" nor a callable.

GraphConfigError

When the node runs, before any tool runs: messages_field does not use add_messages, or tool_calls_field is not declared.

ToolErrorPolicy

ToolErrorPolicy: one of "raise", "return" or Callable[[Exception], str]

What a tool runner does when a tool call fails.

"raise" raises the error, "return" sends its text to the model as the tool result, and a callable turns the exception into that text.

Sub-graphs

subgraph

subgraph(name, graph, *, state_in=None, state_out=None, share=None, child_thread) -> Node

Wrap a graph as a node of another graph.

The child runs on the parent's state store, with the parent's context, on the "main" branch of its thread: a new thread "<parent thread>:<name>:<random hex>" per call with "fresh", or "<parent thread>:<name>", which keeps the child's state, with "stable". An unanswered interrupt in the child pauses the parent under "<task_id>/<child key>". See Sub-agents.

Parameters
namestr

Node name.

graphGraph

Child graph, without a state store of its own.

state_inCallable[[Any], Any] | None

Builds the child input from the parent state.

state_outCallable[[dict[str, Any]], dict[str, Any]] | None

Builds the parent update, a dict, from the child's final state dict.

shareCollection[str] | None

Fields both graphs declare; instead of state_in and state_out.

child_threadLiteral['fresh', 'stable']

Thread policy of the child.

Raises
TypeError

Unless exactly one of these is given: state_in with state_out, or a non-empty share; when the node runs, if state_out returns no dict.

ValueError

If child_thread is not "fresh" or "stable".

GraphConfigError

If the child graph has a state store or does not declare a share field; when the node runs, if the parent does not declare a share field or "stable" runs without a state store.

GraphExecutionError

When the node runs: a shared field with a custom reducer changed, or a "stable" child starts on a forked parent branch or is already running.