uipath-langchain 0.0.112__py3-none-any.whl → 0.1.24__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 (82) hide show
  1. uipath_langchain/_cli/_templates/main.py.template +12 -13
  2. uipath_langchain/_cli/cli_init.py +127 -156
  3. uipath_langchain/_cli/cli_new.py +2 -6
  4. uipath_langchain/_resources/AGENTS.md +21 -0
  5. uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
  6. uipath_langchain/{tracers → _tracing}/__init__.py +0 -2
  7. uipath_langchain/_tracing/_instrument_traceable.py +134 -0
  8. uipath_langchain/_utils/__init__.py +1 -2
  9. uipath_langchain/_utils/_request_mixin.py +351 -54
  10. uipath_langchain/_utils/_settings.py +2 -11
  11. uipath_langchain/agent/exceptions/__init__.py +6 -0
  12. uipath_langchain/agent/exceptions/exceptions.py +11 -0
  13. uipath_langchain/agent/guardrails/__init__.py +21 -0
  14. uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
  15. uipath_langchain/agent/guardrails/actions/base_action.py +23 -0
  16. uipath_langchain/agent/guardrails/actions/block_action.py +41 -0
  17. uipath_langchain/agent/guardrails/actions/escalate_action.py +274 -0
  18. uipath_langchain/agent/guardrails/actions/log_action.py +57 -0
  19. uipath_langchain/agent/guardrails/guardrail_nodes.py +125 -0
  20. uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
  21. uipath_langchain/agent/guardrails/guardrails_subgraph.py +247 -0
  22. uipath_langchain/agent/guardrails/types.py +20 -0
  23. uipath_langchain/agent/react/__init__.py +14 -0
  24. uipath_langchain/agent/react/agent.py +113 -0
  25. uipath_langchain/agent/react/constants.py +2 -0
  26. uipath_langchain/agent/react/init_node.py +20 -0
  27. uipath_langchain/agent/react/llm_node.py +43 -0
  28. uipath_langchain/agent/react/router.py +97 -0
  29. uipath_langchain/agent/react/terminate_node.py +82 -0
  30. uipath_langchain/agent/react/tools/__init__.py +7 -0
  31. uipath_langchain/agent/react/tools/tools.py +50 -0
  32. uipath_langchain/agent/react/types.py +39 -0
  33. uipath_langchain/agent/react/utils.py +49 -0
  34. uipath_langchain/agent/tools/__init__.py +17 -0
  35. uipath_langchain/agent/tools/context_tool.py +53 -0
  36. uipath_langchain/agent/tools/escalation_tool.py +111 -0
  37. uipath_langchain/agent/tools/integration_tool.py +181 -0
  38. uipath_langchain/agent/tools/process_tool.py +49 -0
  39. uipath_langchain/agent/tools/static_args.py +138 -0
  40. uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
  41. uipath_langchain/agent/tools/tool_factory.py +45 -0
  42. uipath_langchain/agent/tools/tool_node.py +22 -0
  43. uipath_langchain/agent/tools/utils.py +11 -0
  44. uipath_langchain/chat/__init__.py +4 -0
  45. uipath_langchain/chat/bedrock.py +187 -0
  46. uipath_langchain/chat/gemini.py +330 -0
  47. uipath_langchain/chat/mapper.py +309 -0
  48. uipath_langchain/chat/models.py +261 -38
  49. uipath_langchain/chat/openai.py +132 -0
  50. uipath_langchain/chat/supported_models.py +42 -0
  51. uipath_langchain/embeddings/embeddings.py +136 -36
  52. uipath_langchain/middlewares.py +0 -2
  53. uipath_langchain/py.typed +0 -0
  54. uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
  55. uipath_langchain/runtime/__init__.py +36 -0
  56. uipath_langchain/runtime/_serialize.py +46 -0
  57. uipath_langchain/runtime/config.py +61 -0
  58. uipath_langchain/runtime/errors.py +43 -0
  59. uipath_langchain/runtime/factory.py +315 -0
  60. uipath_langchain/runtime/graph.py +159 -0
  61. uipath_langchain/runtime/runtime.py +453 -0
  62. uipath_langchain/runtime/schema.py +349 -0
  63. uipath_langchain/runtime/storage.py +115 -0
  64. uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
  65. {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/METADATA +42 -20
  66. uipath_langchain-0.1.24.dist-info/RECORD +76 -0
  67. {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/WHEEL +1 -1
  68. uipath_langchain-0.1.24.dist-info/entry_points.txt +5 -0
  69. uipath_langchain/_cli/_runtime/_context.py +0 -21
  70. uipath_langchain/_cli/_runtime/_exception.py +0 -17
  71. uipath_langchain/_cli/_runtime/_input.py +0 -136
  72. uipath_langchain/_cli/_runtime/_output.py +0 -234
  73. uipath_langchain/_cli/_runtime/_runtime.py +0 -371
  74. uipath_langchain/_cli/_utils/_graph.py +0 -202
  75. uipath_langchain/_cli/cli_run.py +0 -80
  76. uipath_langchain/tracers/AsyncUiPathTracer.py +0 -274
  77. uipath_langchain/tracers/_events.py +0 -33
  78. uipath_langchain/tracers/_instrument_traceable.py +0 -416
  79. uipath_langchain/tracers/_utils.py +0 -52
  80. uipath_langchain-0.0.112.dist-info/RECORD +0 -36
  81. uipath_langchain-0.0.112.dist-info/entry_points.txt +0 -2
  82. {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -0,0 +1,159 @@
1
+ """Graph loading utilities for LangGraph JSON configuration."""
2
+
3
+ import importlib.util
4
+ import inspect
5
+ import logging
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from langgraph.graph import StateGraph
12
+ from langgraph.graph.state import CompiledStateGraph
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class LangGraphLoader:
18
+ """Loads a graph from a Python file path (e.g., 'agent.py:graph')."""
19
+
20
+ def __init__(self, name: str, file_path: str, variable_name: str):
21
+ """
22
+ Initialize the graph loader.
23
+
24
+ Args:
25
+ name: Human-readable name for the graph
26
+ file_path: Path to the Python file containing the graph
27
+ variable_name: Name of the variable/function in the file
28
+ """
29
+ self.name = name
30
+ self.file_path = file_path
31
+ self.variable_name = variable_name
32
+ self._context_manager: Any = None
33
+
34
+ @classmethod
35
+ def from_path_string(cls, name: str, path: str) -> "LangGraphLoader":
36
+ """
37
+ Create a GraphLoader from a path string like 'agent.py:graph'.
38
+
39
+ Args:
40
+ name: Human-readable name for the graph
41
+ path: Path string in format 'file_path:variable_name'
42
+
43
+ Returns:
44
+ GraphLoader instance
45
+ """
46
+ if ":" not in path:
47
+ raise ValueError(f"Invalid path format: {path}. Expected 'file:variable'")
48
+
49
+ file_path, variable_name = path.split(":", 1)
50
+ return cls(name=name, file_path=file_path, variable_name=variable_name)
51
+
52
+ async def load(
53
+ self,
54
+ ) -> StateGraph[Any, Any, Any] | CompiledStateGraph[Any, Any, Any, Any]:
55
+ """
56
+ Load and return the graph.
57
+
58
+ Returns:
59
+ StateGraph or CompiledStateGraph instance
60
+
61
+ Raises:
62
+ ValueError: If file path is outside current directory
63
+ FileNotFoundError: If file doesn't exist
64
+ ImportError: If module can't be loaded
65
+ TypeError: If loaded object isn't a valid graph
66
+ """
67
+ # Validate and normalize paths
68
+ cwd = os.path.abspath(os.getcwd())
69
+ abs_file_path = os.path.abspath(os.path.normpath(self.file_path))
70
+
71
+ if not abs_file_path.startswith(cwd):
72
+ raise ValueError(
73
+ f"Graph file must be within current directory. Got: {self.file_path}"
74
+ )
75
+
76
+ if not os.path.exists(abs_file_path):
77
+ raise FileNotFoundError(f"Graph file not found: {abs_file_path}")
78
+
79
+ # Ensure current directory and src/ are in sys.path
80
+ self._setup_python_path(cwd)
81
+
82
+ # Import the module
83
+ module = self._import_module(abs_file_path)
84
+
85
+ # Get the graph object/function
86
+ graph_obj = getattr(module, self.variable_name, None)
87
+ if graph_obj is None:
88
+ raise AttributeError(
89
+ f"'{self.variable_name}' not found in {self.file_path}"
90
+ )
91
+
92
+ # Resolve the graph (handle functions, async functions, context managers)
93
+ graph = await self._resolve_graph(graph_obj)
94
+
95
+ # Validate it's a valid graph type
96
+ if not isinstance(graph, (StateGraph, CompiledStateGraph)):
97
+ raise TypeError(
98
+ f"Expected StateGraph or CompiledStateGraph, got {type(graph).__name__}"
99
+ )
100
+
101
+ return graph
102
+
103
+ def _setup_python_path(self, cwd: str) -> None:
104
+ """Add current directory and src/ to Python path if needed."""
105
+ if cwd not in sys.path:
106
+ sys.path.insert(0, cwd)
107
+
108
+ # Support src-layout projects (mimics editable install)
109
+ src_dir = os.path.join(cwd, "src")
110
+ if os.path.isdir(src_dir) and src_dir not in sys.path:
111
+ sys.path.insert(0, src_dir)
112
+
113
+ def _import_module(self, abs_file_path: str) -> Any:
114
+ """Import a Python module from a file path."""
115
+ module_name = Path(abs_file_path).stem
116
+ spec = importlib.util.spec_from_file_location(module_name, abs_file_path)
117
+
118
+ if not spec or not spec.loader:
119
+ raise ImportError(f"Could not load module from: {abs_file_path}")
120
+
121
+ module = importlib.util.module_from_spec(spec)
122
+ sys.modules[module_name] = module
123
+ spec.loader.exec_module(module)
124
+
125
+ return module
126
+
127
+ async def _resolve_graph(
128
+ self, graph_obj: Any
129
+ ) -> StateGraph[Any, Any, Any] | CompiledStateGraph[Any, Any, Any, Any]:
130
+ """
131
+ Resolve a graph object that might be:
132
+ - A direct StateGraph/CompiledStateGraph
133
+ - A function that returns a graph
134
+ - An async function that returns a graph
135
+ - An async context manager that yields a graph
136
+ """
137
+ # Handle callable (function or async function)
138
+ if callable(graph_obj):
139
+ if inspect.iscoroutinefunction(graph_obj):
140
+ graph_obj = await graph_obj()
141
+ else:
142
+ graph_obj = graph_obj()
143
+
144
+ # Handle async context manager
145
+ if hasattr(graph_obj, "__aenter__") and callable(graph_obj.__aenter__):
146
+ self._context_manager = graph_obj
147
+ return await graph_obj.__aenter__()
148
+
149
+ return graph_obj
150
+
151
+ async def cleanup(self) -> None:
152
+ """Clean up resources (e.g., exit async context managers)."""
153
+ if self._context_manager:
154
+ try:
155
+ await self._context_manager.__aexit__(None, None, None)
156
+ except Exception as e:
157
+ logger.warning(f"Error during graph cleanup: {e}")
158
+ finally:
159
+ self._context_manager = None