uipath-langchain 0.0.133__py3-none-any.whl → 0.1.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. uipath_langchain/_cli/cli_init.py +130 -191
  2. uipath_langchain/_cli/cli_new.py +2 -3
  3. uipath_langchain/_resources/AGENTS.md +21 -0
  4. uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
  5. uipath_langchain/_tracing/__init__.py +3 -2
  6. uipath_langchain/_tracing/_instrument_traceable.py +11 -12
  7. uipath_langchain/_utils/_request_mixin.py +327 -51
  8. uipath_langchain/_utils/_settings.py +2 -2
  9. uipath_langchain/agent/exceptions/__init__.py +6 -0
  10. uipath_langchain/agent/exceptions/exceptions.py +11 -0
  11. uipath_langchain/agent/guardrails/__init__.py +21 -0
  12. uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
  13. uipath_langchain/agent/guardrails/actions/base_action.py +24 -0
  14. uipath_langchain/agent/guardrails/actions/block_action.py +42 -0
  15. uipath_langchain/agent/guardrails/actions/escalate_action.py +499 -0
  16. uipath_langchain/agent/guardrails/actions/log_action.py +58 -0
  17. uipath_langchain/agent/guardrails/guardrail_nodes.py +173 -0
  18. uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
  19. uipath_langchain/agent/guardrails/guardrails_subgraph.py +283 -0
  20. uipath_langchain/agent/guardrails/types.py +20 -0
  21. uipath_langchain/agent/react/__init__.py +14 -0
  22. uipath_langchain/agent/react/agent.py +117 -0
  23. uipath_langchain/agent/react/constants.py +2 -0
  24. uipath_langchain/agent/react/init_node.py +20 -0
  25. uipath_langchain/agent/react/llm_node.py +43 -0
  26. uipath_langchain/agent/react/router.py +97 -0
  27. uipath_langchain/agent/react/terminate_node.py +82 -0
  28. uipath_langchain/agent/react/tools/__init__.py +7 -0
  29. uipath_langchain/agent/react/tools/tools.py +50 -0
  30. uipath_langchain/agent/react/types.py +39 -0
  31. uipath_langchain/agent/react/utils.py +49 -0
  32. uipath_langchain/agent/tools/__init__.py +17 -0
  33. uipath_langchain/agent/tools/context_tool.py +53 -0
  34. uipath_langchain/agent/tools/escalation_tool.py +111 -0
  35. uipath_langchain/agent/tools/integration_tool.py +181 -0
  36. uipath_langchain/agent/tools/process_tool.py +49 -0
  37. uipath_langchain/agent/tools/static_args.py +138 -0
  38. uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
  39. uipath_langchain/agent/tools/tool_factory.py +45 -0
  40. uipath_langchain/agent/tools/tool_node.py +22 -0
  41. uipath_langchain/agent/tools/utils.py +11 -0
  42. uipath_langchain/chat/__init__.py +4 -0
  43. uipath_langchain/chat/bedrock.py +187 -0
  44. uipath_langchain/chat/mapper.py +309 -0
  45. uipath_langchain/chat/models.py +248 -35
  46. uipath_langchain/chat/openai.py +133 -0
  47. uipath_langchain/chat/supported_models.py +42 -0
  48. uipath_langchain/chat/vertex.py +255 -0
  49. uipath_langchain/embeddings/embeddings.py +131 -34
  50. uipath_langchain/middlewares.py +0 -6
  51. uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
  52. uipath_langchain/runtime/__init__.py +36 -0
  53. uipath_langchain/runtime/_serialize.py +46 -0
  54. uipath_langchain/runtime/config.py +61 -0
  55. uipath_langchain/runtime/errors.py +43 -0
  56. uipath_langchain/runtime/factory.py +315 -0
  57. uipath_langchain/runtime/graph.py +159 -0
  58. uipath_langchain/runtime/runtime.py +453 -0
  59. uipath_langchain/runtime/schema.py +386 -0
  60. uipath_langchain/runtime/storage.py +115 -0
  61. uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
  62. {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.28.dist-info}/METADATA +44 -23
  63. uipath_langchain-0.1.28.dist-info/RECORD +76 -0
  64. {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.28.dist-info}/WHEEL +1 -1
  65. uipath_langchain-0.1.28.dist-info/entry_points.txt +5 -0
  66. uipath_langchain/_cli/_runtime/_context.py +0 -21
  67. uipath_langchain/_cli/_runtime/_conversation.py +0 -298
  68. uipath_langchain/_cli/_runtime/_exception.py +0 -17
  69. uipath_langchain/_cli/_runtime/_input.py +0 -139
  70. uipath_langchain/_cli/_runtime/_output.py +0 -234
  71. uipath_langchain/_cli/_runtime/_runtime.py +0 -379
  72. uipath_langchain/_cli/_utils/_graph.py +0 -199
  73. uipath_langchain/_cli/cli_dev.py +0 -44
  74. uipath_langchain/_cli/cli_eval.py +0 -78
  75. uipath_langchain/_cli/cli_run.py +0 -82
  76. uipath_langchain/_tracing/_oteladapter.py +0 -222
  77. uipath_langchain/_tracing/_utils.py +0 -28
  78. uipath_langchain/builder/agent_config.py +0 -191
  79. uipath_langchain/tools/preconfigured.py +0 -191
  80. uipath_langchain-0.0.133.dist-info/RECORD +0 -41
  81. uipath_langchain-0.0.133.dist-info/entry_points.txt +0 -2
  82. /uipath_langchain/{tools/__init__.py → py.typed} +0 -0
  83. {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.28.dist-info}/licenses/LICENSE +0 -0
@@ -1,24 +1,22 @@
1
- from typing import List, Optional
2
-
3
1
  from langchain_core.callbacks import (
4
2
  AsyncCallbackManagerForRetrieverRun,
5
3
  CallbackManagerForRetrieverRun,
6
4
  )
7
5
  from langchain_core.documents import Document
8
6
  from langchain_core.retrievers import BaseRetriever
9
- from uipath import UiPath
7
+ from uipath.platform import UiPath
10
8
 
11
9
 
12
10
  class ContextGroundingRetriever(BaseRetriever):
13
11
  index_name: str
14
- folder_path: Optional[str] = None
15
- folder_key: Optional[str] = None
16
- uipath_sdk: Optional[UiPath] = None
17
- number_of_results: Optional[int] = 10
12
+ folder_path: str | None = None
13
+ folder_key: str | None = None
14
+ uipath_sdk: UiPath | None = None
15
+ number_of_results: int | None = 10
18
16
 
19
17
  def _get_relevant_documents(
20
18
  self, query: str, *, run_manager: CallbackManagerForRetrieverRun
21
- ) -> List[Document]:
19
+ ) -> list[Document]:
22
20
  """Sync implementations for retriever calls context_grounding API to search the requested index."""
23
21
 
24
22
  sdk = self.uipath_sdk if self.uipath_sdk is not None else UiPath()
@@ -43,7 +41,7 @@ class ContextGroundingRetriever(BaseRetriever):
43
41
 
44
42
  async def _aget_relevant_documents(
45
43
  self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
46
- ) -> List[Document]:
44
+ ) -> list[Document]:
47
45
  """Async implementations for retriever calls context_grounding API to search the requested index."""
48
46
 
49
47
  sdk = self.uipath_sdk if self.uipath_sdk is not None else UiPath()
@@ -0,0 +1,36 @@
1
+ from uipath.runtime import (
2
+ UiPathRuntimeContext,
3
+ UiPathRuntimeFactoryProtocol,
4
+ UiPathRuntimeFactoryRegistry,
5
+ )
6
+
7
+ from uipath_langchain.runtime.factory import UiPathLangGraphRuntimeFactory
8
+ from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime
9
+ from uipath_langchain.runtime.schema import (
10
+ get_entrypoints_schema,
11
+ get_graph_schema,
12
+ )
13
+
14
+
15
+ def register_runtime_factory() -> None:
16
+ """Register the LangGraph factory. Called automatically via entry point."""
17
+
18
+ def create_factory(
19
+ context: UiPathRuntimeContext | None = None,
20
+ ) -> UiPathRuntimeFactoryProtocol:
21
+ return UiPathLangGraphRuntimeFactory(
22
+ context=context if context else UiPathRuntimeContext(),
23
+ )
24
+
25
+ UiPathRuntimeFactoryRegistry.register("langgraph", create_factory, "langgraph.json")
26
+
27
+
28
+ register_runtime_factory()
29
+
30
+ __all__ = [
31
+ "register_runtime_factory",
32
+ "get_entrypoints_schema",
33
+ "get_graph_schema",
34
+ "UiPathLangGraphRuntimeFactory",
35
+ "UiPathLangGraphRuntime",
36
+ ]
@@ -0,0 +1,46 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+
4
+
5
+ def serialize_output(output: Any) -> Any:
6
+ """
7
+ Recursively serialize an output object.
8
+
9
+ Args:
10
+ output: The object to serialize
11
+
12
+ Returns:
13
+ Dict[str, Any]: Serialized output as dictionary
14
+ """
15
+ if output is None:
16
+ return {}
17
+
18
+ # Handle Pydantic models
19
+ if hasattr(output, "model_dump"):
20
+ return serialize_output(output.model_dump(by_alias=True))
21
+ elif hasattr(output, "dict"):
22
+ return serialize_output(output.dict())
23
+ elif hasattr(output, "to_dict"):
24
+ return serialize_output(output.to_dict())
25
+
26
+ # Handle dictionaries
27
+ elif isinstance(output, dict):
28
+ return {k: serialize_output(v) for k, v in output.items()}
29
+
30
+ # Handle lists
31
+ elif isinstance(output, list):
32
+ return [serialize_output(item) for item in output]
33
+
34
+ # Handle other iterables (convert to dict first)
35
+ elif hasattr(output, "__iter__") and not isinstance(output, (str, bytes)):
36
+ try:
37
+ return serialize_output(dict(output))
38
+ except (TypeError, ValueError):
39
+ return output
40
+
41
+ # Handle Enums
42
+ elif isinstance(output, Enum):
43
+ return output.value
44
+
45
+ # Return primitive types as is
46
+ return output
@@ -0,0 +1,61 @@
1
+ """Simple loader for langgraph.json configuration."""
2
+
3
+ import json
4
+ import os
5
+
6
+
7
+ class LangGraphConfig:
8
+ """Simple loader for langgraph.json configuration."""
9
+
10
+ def __init__(self, config_path: str = "langgraph.json"):
11
+ """
12
+ Initialize configuration loader.
13
+
14
+ Args:
15
+ config_path: Path to langgraph.json file
16
+ """
17
+ self.config_path = config_path
18
+ self._graphs: dict[str, str] | None = None
19
+
20
+ @property
21
+ def exists(self) -> bool:
22
+ """Check if langgraph.json exists."""
23
+ return os.path.exists(self.config_path)
24
+
25
+ @property
26
+ def graphs(self) -> dict[str, str]:
27
+ """
28
+ Get graph name -> path mapping from config.
29
+
30
+ Returns:
31
+ Dictionary mapping graph names to file paths (e.g., {"agent": "agent.py:graph"})
32
+ """
33
+ if self._graphs is None:
34
+ self._graphs = self._load_graphs()
35
+ return self._graphs
36
+
37
+ def _load_graphs(self) -> dict[str, str]:
38
+ """Load graph definitions from langgraph.json."""
39
+ if not self.exists:
40
+ raise FileNotFoundError(f"Config file not found: {self.config_path}")
41
+
42
+ try:
43
+ with open(self.config_path, "r") as f:
44
+ config = json.load(f)
45
+
46
+ if "graphs" not in config:
47
+ raise ValueError("Missing required 'graphs' field in langgraph.json")
48
+
49
+ graphs = config["graphs"]
50
+ if not isinstance(graphs, dict):
51
+ raise ValueError("'graphs' must be a dictionary")
52
+
53
+ return graphs
54
+
55
+ except json.JSONDecodeError as e:
56
+ raise ValueError(f"Invalid JSON in {self.config_path}: {e}") from e
57
+
58
+ @property
59
+ def entrypoints(self) -> list[str]:
60
+ """Get list of available graph entrypoints."""
61
+ return list(self.graphs.keys())
@@ -0,0 +1,43 @@
1
+ from enum import Enum
2
+ from typing import Union
3
+
4
+ from uipath.runtime.errors import (
5
+ UiPathBaseRuntimeError,
6
+ UiPathErrorCategory,
7
+ UiPathErrorCode,
8
+ )
9
+
10
+
11
+ class LangGraphErrorCode(Enum):
12
+ CONFIG_MISSING = "CONFIG_MISSING"
13
+ CONFIG_INVALID = "CONFIG_INVALID"
14
+
15
+ GRAPH_NOT_FOUND = "GRAPH_NOT_FOUND"
16
+ GRAPH_IMPORT_ERROR = "GRAPH_IMPORT_ERROR"
17
+ GRAPH_TYPE_ERROR = "GRAPH_TYPE_ERROR"
18
+ GRAPH_VALUE_ERROR = "GRAPH_VALUE_ERROR"
19
+ GRAPH_LOAD_ERROR = "GRAPH_LOAD_ERROR"
20
+ GRAPH_INVALID_UPDATE = "GRAPH_INVALID_UPDATE"
21
+ GRAPH_EMPTY_INPUT = "GRAPH_EMPTY_INPUT"
22
+
23
+ DB_QUERY_FAILED = "DB_QUERY_FAILED"
24
+ DB_TABLE_CREATION_FAILED = "DB_TABLE_CREATION_FAILED"
25
+ HITL_EVENT_CREATION_FAILED = "HITL_EVENT_CREATION_FAILED"
26
+ DB_INSERT_FAILED = "DB_INSERT_FAILED"
27
+ LICENSE_NOT_AVAILABLE = "LICENSE_NOT_AVAILABLE"
28
+
29
+
30
+ class LangGraphRuntimeError(UiPathBaseRuntimeError):
31
+ """Custom exception for LangGraph runtime errors with structured error information."""
32
+
33
+ def __init__(
34
+ self,
35
+ code: Union[LangGraphErrorCode, UiPathErrorCode],
36
+ title: str,
37
+ detail: str,
38
+ category: UiPathErrorCategory = UiPathErrorCategory.UNKNOWN,
39
+ status: int | None = None,
40
+ ):
41
+ super().__init__(
42
+ code.value, title, detail, category, status, prefix="LANGGRAPH"
43
+ )
@@ -0,0 +1,315 @@
1
+ import asyncio
2
+ import os
3
+ from typing import Any, AsyncContextManager
4
+
5
+ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
6
+ from langgraph.graph.state import CompiledStateGraph, StateGraph
7
+ from openinference.instrumentation.langchain import (
8
+ LangChainInstrumentor,
9
+ get_ancestor_spans,
10
+ get_current_span,
11
+ )
12
+ from uipath.core.tracing import UiPathSpanUtils, UiPathTraceManager
13
+ from uipath.platform.resume_triggers import (
14
+ UiPathResumeTriggerHandler,
15
+ )
16
+ from uipath.runtime import (
17
+ UiPathResumableRuntime,
18
+ UiPathRuntimeContext,
19
+ UiPathRuntimeProtocol,
20
+ )
21
+ from uipath.runtime.errors import UiPathErrorCategory
22
+
23
+ from uipath_langchain._tracing import _instrument_traceable_attributes
24
+ from uipath_langchain.runtime.config import LangGraphConfig
25
+ from uipath_langchain.runtime.errors import LangGraphErrorCode, LangGraphRuntimeError
26
+ from uipath_langchain.runtime.graph import LangGraphLoader
27
+ from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime
28
+ from uipath_langchain.runtime.storage import SqliteResumableStorage
29
+
30
+
31
+ class UiPathLangGraphRuntimeFactory:
32
+ """Factory for creating LangGraph runtimes from langgraph.json configuration."""
33
+
34
+ def __init__(
35
+ self,
36
+ context: UiPathRuntimeContext,
37
+ ):
38
+ """
39
+ Initialize the factory.
40
+
41
+ Args:
42
+ context: UiPathRuntimeContext to use for runtime creation
43
+ """
44
+ self.context = context
45
+ self._config: LangGraphConfig | None = None
46
+ self._memory: AsyncSqliteSaver | None = None
47
+ self._memory_cm: AsyncContextManager[AsyncSqliteSaver] | None = None
48
+ self._memory_lock = asyncio.Lock()
49
+
50
+ self._graph_cache: dict[str, CompiledStateGraph[Any, Any, Any, Any]] = {}
51
+ self._graph_loaders: dict[str, LangGraphLoader] = {}
52
+ self._graph_lock = asyncio.Lock()
53
+
54
+ self._setup_instrumentation(self.context.trace_manager)
55
+
56
+ def _setup_instrumentation(self, trace_manager: UiPathTraceManager | None) -> None:
57
+ """Setup tracing and instrumentation."""
58
+ _instrument_traceable_attributes()
59
+ LangChainInstrumentor().instrument()
60
+ UiPathSpanUtils.register_current_span_provider(get_current_span)
61
+ UiPathSpanUtils.register_current_span_ancestors_provider(get_ancestor_spans)
62
+
63
+ def _get_connection_string(self) -> str:
64
+ """Get the database connection string."""
65
+ if self.context.runtime_dir and self.context.state_file:
66
+ path = os.path.join(self.context.runtime_dir, self.context.state_file)
67
+ if not self.context.resume and self.context.job_id is None:
68
+ # If not resuming and no job id, delete the previous state file
69
+ if os.path.exists(path):
70
+ os.remove(path)
71
+ os.makedirs(self.context.runtime_dir, exist_ok=True)
72
+ return path
73
+
74
+ default_path = os.path.join("__uipath", "state.db")
75
+ os.makedirs(os.path.dirname(default_path), exist_ok=True)
76
+ return default_path
77
+
78
+ async def _get_memory(self) -> AsyncSqliteSaver:
79
+ """Get or create the shared memory instance."""
80
+ async with self._memory_lock:
81
+ if self._memory is None:
82
+ connection_string = self._get_connection_string()
83
+ self._memory_cm = AsyncSqliteSaver.from_conn_string(connection_string)
84
+ self._memory = await self._memory_cm.__aenter__()
85
+ await self._memory.setup()
86
+ return self._memory
87
+
88
+ def _load_config(self) -> LangGraphConfig:
89
+ """Load langgraph.json configuration."""
90
+ if self._config is None:
91
+ self._config = LangGraphConfig()
92
+ return self._config
93
+
94
+ async def _load_graph(
95
+ self, entrypoint: str
96
+ ) -> StateGraph[Any, Any, Any] | CompiledStateGraph[Any, Any, Any, Any]:
97
+ """
98
+ Load a graph for the given entrypoint.
99
+
100
+ Args:
101
+ entrypoint: Name of the graph to load
102
+
103
+ Returns:
104
+ The loaded StateGraph or CompiledStateGraph
105
+
106
+ Raises:
107
+ LangGraphRuntimeError: If graph cannot be loaded
108
+ """
109
+ config = self._load_config()
110
+ if not config.exists:
111
+ raise LangGraphRuntimeError(
112
+ LangGraphErrorCode.CONFIG_MISSING,
113
+ "Invalid configuration",
114
+ "Failed to load configuration",
115
+ UiPathErrorCategory.DEPLOYMENT,
116
+ )
117
+
118
+ if entrypoint not in config.graphs:
119
+ available = ", ".join(config.entrypoints)
120
+ raise LangGraphRuntimeError(
121
+ LangGraphErrorCode.GRAPH_NOT_FOUND,
122
+ "Graph not found",
123
+ f"Graph '{entrypoint}' not found. Available: {available}",
124
+ UiPathErrorCategory.DEPLOYMENT,
125
+ )
126
+
127
+ path = config.graphs[entrypoint]
128
+ graph_loader = LangGraphLoader.from_path_string(entrypoint, path)
129
+
130
+ self._graph_loaders[entrypoint] = graph_loader
131
+
132
+ try:
133
+ return await graph_loader.load()
134
+
135
+ except ImportError as e:
136
+ raise LangGraphRuntimeError(
137
+ LangGraphErrorCode.GRAPH_IMPORT_ERROR,
138
+ "Graph import failed",
139
+ f"Failed to import graph '{entrypoint}': {str(e)}",
140
+ UiPathErrorCategory.USER,
141
+ ) from e
142
+ except TypeError as e:
143
+ raise LangGraphRuntimeError(
144
+ LangGraphErrorCode.GRAPH_TYPE_ERROR,
145
+ "Invalid graph type",
146
+ f"Graph '{entrypoint}' is not a valid StateGraph or CompiledStateGraph: {str(e)}",
147
+ UiPathErrorCategory.USER,
148
+ ) from e
149
+ except ValueError as e:
150
+ raise LangGraphRuntimeError(
151
+ LangGraphErrorCode.GRAPH_VALUE_ERROR,
152
+ "Invalid graph value",
153
+ f"Invalid value in graph '{entrypoint}': {str(e)}",
154
+ UiPathErrorCategory.USER,
155
+ ) from e
156
+ except Exception as e:
157
+ raise LangGraphRuntimeError(
158
+ LangGraphErrorCode.GRAPH_LOAD_ERROR,
159
+ "Failed to load graph",
160
+ f"Unexpected error loading graph '{entrypoint}': {str(e)}",
161
+ UiPathErrorCategory.USER,
162
+ ) from e
163
+
164
+ async def _compile_graph(
165
+ self,
166
+ graph: StateGraph[Any, Any, Any] | CompiledStateGraph[Any, Any, Any, Any],
167
+ memory: AsyncSqliteSaver,
168
+ ) -> CompiledStateGraph[Any, Any, Any, Any]:
169
+ """
170
+ Compile a graph with the given memory/checkpointer.
171
+
172
+ Args:
173
+ graph: The graph to compile (StateGraph or already compiled)
174
+ memory: Checkpointer to use for compiled graph
175
+
176
+ Returns:
177
+ The compiled StateGraph
178
+ """
179
+ builder = graph.builder if isinstance(graph, CompiledStateGraph) else graph
180
+
181
+ return builder.compile(checkpointer=memory)
182
+
183
+ async def _resolve_and_compile_graph(
184
+ self, entrypoint: str, memory: AsyncSqliteSaver
185
+ ) -> CompiledStateGraph[Any, Any, Any, Any]:
186
+ """
187
+ Resolve a graph from configuration and compile it.
188
+ Results are cached for reuse across multiple runtime instances.
189
+
190
+ Args:
191
+ entrypoint: Name of the graph to resolve
192
+ memory: Checkpointer to use for compiled graph
193
+
194
+ Returns:
195
+ The compiled StateGraph ready for execution
196
+
197
+ Raises:
198
+ LangGraphRuntimeError: If resolution or compilation fails
199
+ """
200
+ async with self._graph_lock:
201
+ if entrypoint in self._graph_cache:
202
+ return self._graph_cache[entrypoint]
203
+
204
+ loaded_graph = await self._load_graph(entrypoint)
205
+
206
+ compiled_graph = await self._compile_graph(loaded_graph, memory)
207
+
208
+ self._graph_cache[entrypoint] = compiled_graph
209
+
210
+ return compiled_graph
211
+
212
+ def discover_entrypoints(self) -> list[str]:
213
+ """
214
+ Discover all graph entrypoints.
215
+
216
+ Returns:
217
+ List of graph names that can be used as entrypoints
218
+ """
219
+ config = self._load_config()
220
+ if not config.exists:
221
+ return []
222
+ return config.entrypoints
223
+
224
+ async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
225
+ """
226
+ Discover runtime instances for all entrypoints.
227
+
228
+ Returns:
229
+ List of LangGraphRuntime instances, one per entrypoint
230
+ """
231
+ entrypoints = self.discover_entrypoints()
232
+ memory = await self._get_memory()
233
+
234
+ runtimes: list[UiPathRuntimeProtocol] = []
235
+ for entrypoint in entrypoints:
236
+ compiled_graph = await self._resolve_and_compile_graph(entrypoint, memory)
237
+
238
+ runtime = await self._create_runtime_instance(
239
+ compiled_graph=compiled_graph,
240
+ runtime_id=entrypoint,
241
+ entrypoint=entrypoint,
242
+ )
243
+ runtimes.append(runtime)
244
+
245
+ return runtimes
246
+
247
+ async def _create_runtime_instance(
248
+ self,
249
+ compiled_graph: CompiledStateGraph[Any, Any, Any, Any],
250
+ runtime_id: str,
251
+ entrypoint: str,
252
+ ) -> UiPathRuntimeProtocol:
253
+ """
254
+ Create a runtime instance from a compiled graph.
255
+
256
+ Args:
257
+ compiled_graph: The compiled graph
258
+ runtime_id: Unique identifier for the runtime instance
259
+ entrypoint: Graph entrypoint name
260
+
261
+ Returns:
262
+ Configured runtime instance
263
+ """
264
+ base_runtime = UiPathLangGraphRuntime(
265
+ graph=compiled_graph,
266
+ runtime_id=runtime_id,
267
+ entrypoint=entrypoint,
268
+ )
269
+
270
+ memory = await self._get_memory()
271
+ storage = SqliteResumableStorage(memory)
272
+ trigger_manager = UiPathResumeTriggerHandler()
273
+
274
+ return UiPathResumableRuntime(
275
+ delegate=base_runtime,
276
+ storage=storage,
277
+ trigger_manager=trigger_manager,
278
+ )
279
+
280
+ async def new_runtime(
281
+ self, entrypoint: str, runtime_id: str
282
+ ) -> UiPathRuntimeProtocol:
283
+ """
284
+ Create a new LangGraph runtime instance.
285
+
286
+ Args:
287
+ entrypoint: Graph name from langgraph.json
288
+ runtime_id: Unique identifier for the runtime instance
289
+
290
+ Returns:
291
+ Configured runtime instance with compiled graph
292
+ """
293
+ # Get shared memory instance
294
+ memory = await self._get_memory()
295
+
296
+ compiled_graph = await self._resolve_and_compile_graph(entrypoint, memory)
297
+
298
+ return await self._create_runtime_instance(
299
+ compiled_graph=compiled_graph,
300
+ runtime_id=runtime_id,
301
+ entrypoint=entrypoint,
302
+ )
303
+
304
+ async def dispose(self) -> None:
305
+ """Cleanup factory resources."""
306
+ for loader in self._graph_loaders.values():
307
+ await loader.cleanup()
308
+
309
+ self._graph_loaders.clear()
310
+ self._graph_cache.clear()
311
+
312
+ if self._memory_cm is not None:
313
+ await self._memory_cm.__aexit__(None, None, None)
314
+ self._memory_cm = None
315
+ self._memory = None