Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,31 @@ def convert(
converted_components: Optional[Dict[str, Any]] = None,
checkpointer: Optional[Checkpointer] = None,
config: Optional[RunnableConfig] = None,
middleware: Optional[List[Any]] = None,
**kwargs: Any,
) -> Any:
"""Convert the given PyAgentSpec component object into the corresponding LangGraph component"""
"""Convert the given PyAgentSpec component object into the corresponding LangGraph component.

Parameters
----------
agentspec_component:
The Agent Spec component to convert.
tool_registry:
Dictionary mapping tool names to LangGraph tool objects.
converted_components:
Optional cache of already-converted components (keyed by component id).
checkpointer:
Optional LangGraph checkpointer to wire into created graphs.
config:
Optional ``RunnableConfig`` to pass to created runnables/graphs.
middleware:
Optional list of LangChain agent middleware instances forwarded to
``langchain_agents.create_agent(middleware=...)`` when compiling an Agent
Spec ``Agent`` into a ReAct graph. Order is preserved — index ``0`` is the
outermost middleware. When ``None`` or an empty list, the ``middleware``
keyword is omitted entirely from the ``create_agent`` call.
"""
middleware_list: List[Any] = list(middleware or [])
if converted_components is None:
converted_components = {}
if config is None:
Expand All @@ -209,7 +231,12 @@ def convert(
config = RunnableConfig({})
if agentspec_component.id not in converted_components:
converted_components[agentspec_component.id] = self._convert(
agentspec_component, tool_registry, converted_components, checkpointer, config
agentspec_component,
tool_registry,
converted_components,
checkpointer,
config,
middleware_list,
)
return converted_components[agentspec_component.id]

Expand All @@ -220,6 +247,7 @@ def _convert(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> Any:
if isinstance(agentspec_component, AgentSpecAgent):
return self._agent_convert_to_langgraph(
Expand All @@ -228,6 +256,7 @@ def _convert(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(agentspec_component, AgentSpecSwarm):
return self._swarm_convert_to_langgraph(
Expand All @@ -236,6 +265,7 @@ def _convert(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(agentspec_component, AgentSpecLlmConfig):
return self._llm_convert_to_langgraph(agentspec_component, config=config)
Expand Down Expand Up @@ -274,6 +304,7 @@ def _convert(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(agentspec_component, AgentSpecNode):
return self._node_convert_to_langgraph(
Expand All @@ -282,6 +313,7 @@ def _convert(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(agentspec_component, AgentSpecComponent):
raise NotImplementedError(
Expand Down Expand Up @@ -325,6 +357,7 @@ def _flow_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> CompiledStateGraph[Any, Any, Any]:

graph_builder = StateGraph(
Expand All @@ -340,6 +373,7 @@ def _flow_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
for node in flow.nodes
}
Expand Down Expand Up @@ -520,6 +554,7 @@ def _node_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> "NodeExecutor":
if isinstance(node, AgentSpecStartNode):
return self._start_node_convert_to_langgraph(node)
Expand Down Expand Up @@ -548,6 +583,7 @@ def _node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(node, AgentSpecBranchingNode):
return self._branching_node_convert_to_langgraph(node)
Expand All @@ -560,6 +596,7 @@ def _node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(node, AgentSpecCatchExceptionNode):
return self._catch_exception_node_convert_to_langgraph(
Expand All @@ -568,6 +605,7 @@ def _node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
elif isinstance(node, AgentSpecInputMessageNode):
return self._input_message_node_convert_to_langgraph(node)
Expand All @@ -580,6 +618,7 @@ def _node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
else:
raise NotImplementedError(
Expand Down Expand Up @@ -609,6 +648,7 @@ def _map_node_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> "NodeExecutor":
from pyagentspec.adapters.langgraph._node_execution import MapNodeExecutor

Expand All @@ -618,6 +658,7 @@ def _map_node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
if not isinstance(subflow, CompiledStateGraph):
raise TypeError("MapNodeExecutor can only be initialized with MapNode")
Expand All @@ -631,6 +672,7 @@ def _flow_node_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> "NodeExecutor":
from pyagentspec.adapters.langgraph._node_execution import FlowNodeExecutor

Expand All @@ -640,6 +682,7 @@ def _flow_node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
if not isinstance(subflow, CompiledStateGraph):
raise TypeError("FlowNodeExecutor can only initialize FlowNode")
Expand All @@ -657,6 +700,7 @@ def _catch_exception_node_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> "NodeExecutor":
from pyagentspec.adapters.langgraph._node_execution import CatchExceptionNodeExecutor

Expand All @@ -666,6 +710,7 @@ def _catch_exception_node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
if not isinstance(subflow, CompiledStateGraph):
raise TypeError(
Expand Down Expand Up @@ -698,6 +743,7 @@ def _agent_node_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> "NodeExecutor":
from pyagentspec.adapters.langgraph._node_execution import AgentNodeExecutor

Expand All @@ -707,6 +753,7 @@ def _agent_node_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)

def _llm_node_convert_to_langgraph(
Expand Down Expand Up @@ -993,6 +1040,7 @@ def _swarm_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> CompiledStateGraph[Any, Any, Any]:
if agentspec_component.handoff is AgentSpecHandoffMode.NEVER:
# As of now, we cannot control what langgraph-swarm does internally in terms of conversation sharing.
Expand Down Expand Up @@ -1023,6 +1071,7 @@ def _swarm_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)
handoffs: dict[str, list[str]] = {agent_name: [] for agent_name in agents}
for from_agent, to_agent in agentspec_component.relationships:
Expand All @@ -1042,6 +1091,7 @@ def _swarm_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
additional_langgraph_tools=[
langgraph_swarm.create_handoff_tool(agent_name=to_agent_name)
for to_agent_name in handoffs.get(agent.name, [])
Expand Down Expand Up @@ -1069,6 +1119,7 @@ def _create_react_agent_with_given_info(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
additional_langgraph_tools: Optional[List[LangGraphTool]] = None,
) -> CompiledStateGraph[Any, Any, Any]:
model = self.convert(
Expand Down Expand Up @@ -1115,7 +1166,7 @@ def _create_react_agent_with_given_info(
inputs=inputs,
)

compiled_graph = langchain_agents.create_agent(
create_agent_kwargs: Dict[str, Any] = dict(
name=name,
model=model,
tools=langgraph_tools,
Expand All @@ -1124,6 +1175,9 @@ def _create_react_agent_with_given_info(
response_format=output_model,
state_schema=state_schema,
)
if middleware:
create_agent_kwargs["middleware"] = middleware
compiled_graph = langchain_agents.create_agent(**create_agent_kwargs)

# To enable flow execution traces monkey patch all the functions that invoke the compiled graph

Expand Down Expand Up @@ -1209,6 +1263,7 @@ def _agent_convert_to_langgraph(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: List[Any],
) -> CompiledStateGraph[Any, Any, Any]:
return self._create_react_agent_with_given_info(
name=agentspec_component.name,
Expand All @@ -1223,6 +1278,7 @@ def _agent_convert_to_langgraph(
converted_components=converted_components,
checkpointer=checkpointer,
config=config,
middleware=middleware,
)

def _llm_convert_to_langgraph(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ def __init__(
converted_components: Dict[str, Any],
checkpointer: Optional[Checkpointer],
config: RunnableConfig,
middleware: Optional[List[Any]] = None,
) -> None:
super().__init__(node)
if not isinstance(self.node, AgentSpecAgentNode):
Expand All @@ -495,6 +496,7 @@ def __init__(
self.checkpointer = checkpointer
self.converted_components = converted_components
self.config = config
self._middleware: List[Any] = list(middleware or [])
self._agents_cache: Dict[str, CompiledStateGraph[Any, Any]] = {}

def _create_react_agent_with_given_input_values(
Expand Down Expand Up @@ -523,6 +525,7 @@ def _create_react_agent_with_given_input_values(
converted_components=self.converted_components,
checkpointer=self.checkpointer,
config=self.config,
middleware=self._middleware,
)
return self._agents_cache[system_prompt]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class AgentSpecLoader(AdapterAgnosticAgentSpecLoader):
enables features that require a checkpointer (e.g., client tools).
config:
Optional ``RunnableConfig`` to pass to created runnables/graphs.
middleware:
Optional list of LangChain agent middleware instances forwarded verbatim to
``langchain_agents.create_agent(middleware=...)`` when compiling an Agent Spec
``Agent`` into a ReAct graph. Order is preserved — index ``0`` is the outermost
middleware. When ``None`` or an empty list, the ``middleware`` keyword is
omitted entirely from the ``create_agent`` call.
allowed_components:
Optional iterable of Agent Spec component type names or Component classes allowed
to be loaded. If omitted, all component types are allowed unless blocked.
Expand All @@ -57,7 +63,7 @@ def __init__(
plugins: Optional[List[ComponentDeserializationPlugin]] = None,
checkpointer: Optional[Checkpointer] = None,
config: Optional[RunnableConfig] = None,
*,
middleware: Optional[List[Any]] = None,
allowed_components: Optional[ComponentPolicyInput] = None,
blocked_components: Optional[ComponentPolicyInput] = None,
) -> None:
Expand All @@ -69,6 +75,7 @@ def __init__(
)
self.checkpointer = checkpointer
self.config = config
self._middleware: List[Any] = list(middleware or [])

@property
def agentspec_to_runtime_converter(self) -> AgentSpecToLangGraphConverter:
Expand Down Expand Up @@ -287,5 +294,6 @@ def load_component(self, agentspec_component: AgentSpecComponent) -> LangGraphRu
tool_registry=self.tool_registry,
checkpointer=self.checkpointer,
config=self.config,
middleware=self._middleware,
),
)
Loading