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
@@ -1,371 +0,0 @@
1
- import json
2
- import logging
3
- import os
4
- from typing import Any, Dict, List, Optional, Tuple, Union
5
-
6
- from langchain_core.callbacks.base import BaseCallbackHandler
7
- from langchain_core.messages import BaseMessage
8
- from langchain_core.runnables.config import RunnableConfig
9
- from langchain_core.tracers.langchain import wait_for_all_tracers
10
- from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
11
- from langgraph.errors import EmptyInputError, GraphRecursionError, InvalidUpdateError
12
- from langgraph.graph.state import CompiledStateGraph
13
- from uipath._cli._runtime._contracts import (
14
- UiPathBaseRuntime,
15
- UiPathErrorCategory,
16
- UiPathRuntimeResult,
17
- )
18
-
19
- from ..._utils import _instrument_traceable_attributes
20
- from ...tracers import AsyncUiPathTracer
21
- from ._context import LangGraphRuntimeContext
22
- from ._exception import LangGraphRuntimeError
23
- from ._input import LangGraphInputProcessor
24
- from ._output import LangGraphOutputProcessor
25
-
26
- logger = logging.getLogger(__name__)
27
-
28
-
29
- class LangGraphRuntime(UiPathBaseRuntime):
30
- """
31
- A runtime class implementing the async context manager protocol.
32
- This allows using the class with 'async with' statements.
33
- """
34
-
35
- def __init__(self, context: LangGraphRuntimeContext):
36
- super().__init__(context)
37
- self.context: LangGraphRuntimeContext = context
38
-
39
- async def execute(self) -> Optional[UiPathRuntimeResult]:
40
- """
41
- Execute the graph with the provided input and configuration.
42
-
43
- Returns:
44
- Dictionary with execution results
45
-
46
- Raises:
47
- LangGraphRuntimeError: If execution fails
48
- """
49
- _instrument_traceable_attributes()
50
-
51
- await self.validate()
52
-
53
- if self.context.state_graph is None:
54
- return None
55
-
56
- tracer = None
57
-
58
- try:
59
- if self.context.resume is False and self.context.job_id is None:
60
- # Delete the previous graph state file at debug time
61
- if os.path.exists(self.state_file_path):
62
- os.remove(self.state_file_path)
63
-
64
- async with AsyncSqliteSaver.from_conn_string(
65
- self.state_file_path
66
- ) as memory:
67
- self.context.memory = memory
68
-
69
- # Compile the graph with the checkpointer
70
- graph = self.context.state_graph.compile(
71
- checkpointer=self.context.memory
72
- )
73
-
74
- # Process input, handling resume if needed
75
- input_processor = LangGraphInputProcessor(context=self.context)
76
-
77
- processed_input = await input_processor.process()
78
-
79
- # Set up tracing if available
80
- callbacks: List[BaseCallbackHandler] = []
81
-
82
- if self.context.job_id and self.context.tracing_enabled:
83
- tracer = AsyncUiPathTracer(context=self.context.trace_context)
84
- callbacks = [tracer]
85
-
86
- graph_config: RunnableConfig = {
87
- "configurable": {
88
- "thread_id": self.context.job_id
89
- if self.context.job_id
90
- else "default"
91
- },
92
- "callbacks": callbacks,
93
- }
94
-
95
- recursion_limit = os.environ.get("LANGCHAIN_RECURSION_LIMIT", None)
96
- max_concurrency = os.environ.get("LANGCHAIN_MAX_CONCURRENCY", None)
97
-
98
- if recursion_limit is not None:
99
- graph_config["recursion_limit"] = int(recursion_limit)
100
- if max_concurrency is not None:
101
- graph_config["max_concurrency"] = int(max_concurrency)
102
-
103
- # Stream the output at debug time
104
- if self.context.job_id is None:
105
- # Get final chunk while streaming
106
- final_chunk = None
107
- async for stream_chunk in graph.astream(
108
- processed_input,
109
- graph_config,
110
- stream_mode="updates",
111
- subgraphs=True,
112
- ):
113
- self._pretty_print(stream_chunk)
114
- final_chunk = stream_chunk
115
-
116
- self.context.output = self._extract_graph_result(final_chunk, graph)
117
- else:
118
- # Execute the graph normally at runtime
119
- self.context.output = await graph.ainvoke(
120
- processed_input, graph_config
121
- )
122
-
123
- # Get the state if available
124
- try:
125
- self.context.state = await graph.aget_state(graph_config)
126
- except Exception:
127
- pass
128
-
129
- output_processor = await LangGraphOutputProcessor.create(self.context)
130
-
131
- self.context.result = await output_processor.process()
132
-
133
- return self.context.result
134
-
135
- except Exception as e:
136
- if isinstance(e, LangGraphRuntimeError):
137
- raise
138
-
139
- detail = f"Error: {str(e)}"
140
-
141
- if isinstance(e, GraphRecursionError):
142
- raise LangGraphRuntimeError(
143
- "GRAPH_RECURSION_ERROR",
144
- "Graph recursion limit exceeded",
145
- detail,
146
- UiPathErrorCategory.USER,
147
- ) from e
148
-
149
- if isinstance(e, InvalidUpdateError):
150
- raise LangGraphRuntimeError(
151
- "GRAPH_INVALID_UPDATE",
152
- str(e),
153
- detail,
154
- UiPathErrorCategory.USER,
155
- ) from e
156
-
157
- if isinstance(e, EmptyInputError):
158
- raise LangGraphRuntimeError(
159
- "GRAPH_EMPTY_INPUT",
160
- "The input data is empty",
161
- detail,
162
- UiPathErrorCategory.USER,
163
- ) from e
164
-
165
- raise LangGraphRuntimeError(
166
- "EXECUTION_ERROR",
167
- "Graph execution failed",
168
- detail,
169
- UiPathErrorCategory.USER,
170
- ) from e
171
- finally:
172
- if tracer is not None:
173
- await tracer.wait_for_all_tracers()
174
-
175
- if self.context.langsmith_tracing_enabled:
176
- wait_for_all_tracers()
177
-
178
- async def validate(self) -> None:
179
- """Validate runtime inputs."""
180
- """Load and validate the graph configuration ."""
181
- try:
182
- if self.context.input:
183
- self.context.input_json = json.loads(self.context.input)
184
- except json.JSONDecodeError as e:
185
- raise LangGraphRuntimeError(
186
- "INPUT_INVALID_JSON",
187
- "Invalid JSON input",
188
- "The input data is not valid JSON.",
189
- UiPathErrorCategory.USER,
190
- ) from e
191
-
192
- if self.context.langgraph_config is None:
193
- raise LangGraphRuntimeError(
194
- "CONFIG_MISSING",
195
- "Invalid configuration",
196
- "Failed to load configuration",
197
- UiPathErrorCategory.DEPLOYMENT,
198
- )
199
-
200
- try:
201
- self.context.langgraph_config.load_config()
202
- except Exception as e:
203
- raise LangGraphRuntimeError(
204
- "CONFIG_INVALID",
205
- "Invalid configuration",
206
- f"Failed to load configuration: {str(e)}",
207
- UiPathErrorCategory.DEPLOYMENT,
208
- ) from e
209
-
210
- # Determine entrypoint if not provided
211
- graphs = self.context.langgraph_config.graphs
212
- if not self.context.entrypoint and len(graphs) == 1:
213
- self.context.entrypoint = graphs[0].name
214
- elif not self.context.entrypoint:
215
- graph_names = ", ".join(g.name for g in graphs)
216
- raise LangGraphRuntimeError(
217
- "ENTRYPOINT_MISSING",
218
- "Entrypoint required",
219
- f"Multiple graphs available. Please specify one of: {graph_names}.",
220
- UiPathErrorCategory.DEPLOYMENT,
221
- )
222
-
223
- # Get the specified graph
224
- self.graph_config = self.context.langgraph_config.get_graph(
225
- self.context.entrypoint
226
- )
227
- if not self.graph_config:
228
- raise LangGraphRuntimeError(
229
- "GRAPH_NOT_FOUND",
230
- "Graph not found",
231
- f"Graph '{self.context.entrypoint}' not found.",
232
- UiPathErrorCategory.DEPLOYMENT,
233
- )
234
- try:
235
- loaded_graph = await self.graph_config.load_graph()
236
- self.context.state_graph = (
237
- loaded_graph.builder
238
- if isinstance(loaded_graph, CompiledStateGraph)
239
- else loaded_graph
240
- )
241
- except ImportError as e:
242
- raise LangGraphRuntimeError(
243
- "GRAPH_IMPORT_ERROR",
244
- "Graph import failed",
245
- f"Failed to import graph '{self.context.entrypoint}': {str(e)}",
246
- UiPathErrorCategory.USER,
247
- ) from e
248
- except TypeError as e:
249
- raise LangGraphRuntimeError(
250
- "GRAPH_TYPE_ERROR",
251
- "Invalid graph type",
252
- f"Graph '{self.context.entrypoint}' is not a valid StateGraph or CompiledStateGraph: {str(e)}",
253
- UiPathErrorCategory.USER,
254
- ) from e
255
- except ValueError as e:
256
- raise LangGraphRuntimeError(
257
- "GRAPH_VALUE_ERROR",
258
- "Invalid graph value",
259
- f"Invalid value in graph '{self.context.entrypoint}': {str(e)}",
260
- UiPathErrorCategory.USER,
261
- ) from e
262
- except Exception as e:
263
- raise LangGraphRuntimeError(
264
- "GRAPH_LOAD_ERROR",
265
- "Failed to load graph",
266
- f"Unexpected error loading graph '{self.context.entrypoint}': {str(e)}",
267
- UiPathErrorCategory.USER,
268
- ) from e
269
-
270
- async def cleanup(self):
271
- if hasattr(self, "graph_config") and self.graph_config:
272
- await self.graph_config.cleanup()
273
-
274
- def _extract_graph_result(self, final_chunk, graph: CompiledStateGraph):
275
- """
276
- Extract the result from a LangGraph output chunk according to the graph's output channels.
277
-
278
- Args:
279
- final_chunk: The final chunk from graph.astream()
280
- graph: The LangGraph instance
281
-
282
- Returns:
283
- The extracted result according to the graph's output_channels configuration
284
- """
285
- # Unwrap from subgraph tuple format if needed
286
- if isinstance(final_chunk, tuple) and len(final_chunk) == 2:
287
- final_chunk = final_chunk[
288
- 1
289
- ] # Extract data part from (namespace, data) tuple
290
-
291
- # If the result isn't a dict or graph doesn't define output channels, return as is
292
- if not isinstance(final_chunk, dict) or not hasattr(graph, "output_channels"):
293
- return final_chunk
294
-
295
- output_channels = graph.output_channels
296
-
297
- # Case 1: Single output channel as string
298
- if isinstance(output_channels, str):
299
- if output_channels in final_chunk:
300
- return final_chunk[output_channels]
301
- else:
302
- return final_chunk
303
-
304
- # Case 2: Multiple output channels as sequence
305
- elif hasattr(output_channels, "__iter__") and not isinstance(
306
- output_channels, str
307
- ):
308
- # Check which channels are present
309
- available_channels = [ch for ch in output_channels if ch in final_chunk]
310
-
311
- if available_channels:
312
- # Create a dict with the available channels
313
- return {channel: final_chunk[channel] for channel in available_channels}
314
-
315
- # Fallback for any other case
316
- return final_chunk
317
-
318
- def _pretty_print(self, stream_chunk: Union[Tuple[Any, Any], Dict[str, Any], Any]):
319
- """
320
- Pretty print a chunk from a LangGraph stream with stream_mode="updates" and subgraphs=True.
321
-
322
- Args:
323
- stream_chunk: A tuple of (namespace, updates) from graph.astream()
324
- """
325
- if not isinstance(stream_chunk, tuple) or len(stream_chunk) < 2:
326
- return
327
-
328
- node_namespace = ""
329
- chunk_namespace = stream_chunk[0]
330
- node_updates = stream_chunk[1]
331
-
332
- # Extract namespace if available
333
- if chunk_namespace and len(chunk_namespace) > 0:
334
- node_namespace = chunk_namespace[0]
335
-
336
- if not isinstance(node_updates, dict):
337
- logger.info("Raw update: %s", node_updates)
338
- return
339
-
340
- # Process each node's updates
341
- for node_name, node_result in node_updates.items():
342
- # Log node identifier with appropriate namespace context
343
- if node_namespace:
344
- logger.info("[%s][%s]", node_namespace, node_name)
345
- else:
346
- logger.info("[%s]", node_name)
347
-
348
- # Handle non-dict results
349
- if not isinstance(node_result, dict):
350
- logger.info("%s", node_result)
351
- continue
352
-
353
- # Process messages specially
354
- messages = node_result.get("messages", [])
355
- if isinstance(messages, list):
356
- for message in messages:
357
- if isinstance(message, BaseMessage):
358
- message.pretty_print()
359
-
360
- # Exclude "messages" from node_result and pretty-print the rest
361
- metadata = {k: v for k, v in node_result.items() if k != "messages"}
362
- if metadata:
363
- try:
364
- formatted_metadata = json.dumps(
365
- metadata,
366
- indent=2,
367
- ensure_ascii=False,
368
- )
369
- logger.info("%s", formatted_metadata)
370
- except (TypeError, ValueError):
371
- pass
@@ -1,202 +0,0 @@
1
- import importlib.util
2
- import inspect
3
- import json
4
- import logging
5
- import os
6
- import sys
7
- from dataclasses import dataclass
8
- from pathlib import Path
9
- from typing import Any, Dict, List, Optional, Union, cast
10
-
11
- from dotenv import load_dotenv
12
- from langgraph.graph import StateGraph
13
- from langgraph.graph.state import CompiledStateGraph
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- @dataclass
19
- class GraphConfig:
20
- name: str
21
- path: str
22
- file_path: str
23
- graph_var: str
24
- _graph: Optional[Union[StateGraph, CompiledStateGraph]] = None
25
-
26
- @classmethod
27
- def from_config(cls, name: str, path: str) -> "GraphConfig":
28
- file_path, graph_var = path.split(":")
29
- return cls(name=name, path=path, file_path=file_path, graph_var=graph_var)
30
-
31
- async def load_graph(self) -> Union[StateGraph, CompiledStateGraph]:
32
- """Load graph from the specified path"""
33
- try:
34
- cwd = os.path.abspath(os.getcwd())
35
- abs_file_path = os.path.abspath(os.path.normpath(self.file_path))
36
-
37
- if not abs_file_path.startswith(cwd):
38
- raise ValueError(
39
- f"Script path must be within the current directory. Found: {self.file_path}"
40
- )
41
-
42
- if not os.path.exists(abs_file_path):
43
- raise FileNotFoundError(f"Script not found: {abs_file_path}")
44
-
45
- if cwd not in sys.path:
46
- sys.path.insert(0, cwd)
47
-
48
- module_name = Path(abs_file_path).stem
49
- spec = importlib.util.spec_from_file_location(module_name, abs_file_path)
50
-
51
- if not spec or not spec.loader:
52
- raise ImportError(f"Could not load module from: {abs_file_path}")
53
-
54
- module = importlib.util.module_from_spec(spec)
55
- sys.modules[module_name] = module
56
- spec.loader.exec_module(module)
57
-
58
- graph = getattr(module, self.graph_var, None)
59
-
60
- # Get the graph object or function
61
- graph_obj = getattr(module, self.graph_var, None)
62
-
63
- # Handle callable graph factory
64
- if callable(graph_obj):
65
- if inspect.iscoroutinefunction(graph_obj):
66
- # Handle async function
67
- try:
68
- graph_obj = await graph_obj()
69
- except RuntimeError as e:
70
- raise e
71
- else:
72
- # Call regular function
73
- graph_obj = graph_obj()
74
-
75
- # Handle async context manager
76
- if (
77
- graph_obj is not None
78
- and hasattr(graph_obj, "__aenter__")
79
- and callable(graph_obj.__aenter__)
80
- ):
81
- self._context_manager = graph_obj
82
- graph = await graph_obj.__aenter__()
83
-
84
- # No need for atexit registration - the calling code should
85
- # maintain a reference to this object and call cleanup explicitly
86
-
87
- else:
88
- # Not a context manager, use directly
89
- graph = graph_obj
90
-
91
- if not isinstance(graph, (StateGraph, CompiledStateGraph)):
92
- raise TypeError(
93
- f"Expected StateGraph, CompiledStateGraph, or a callable returning one of these, got {type(graph)}"
94
- )
95
-
96
- self._graph = graph
97
- return graph
98
-
99
- except Exception as e:
100
- logger.error(f"Failed to load graph {self.name}: {str(e)}")
101
- raise
102
-
103
- async def get_input_schema(self) -> Dict[str, Any]:
104
- """Extract input schema from graph"""
105
- if not self._graph:
106
- self._graph = await self.load_graph()
107
-
108
- if hasattr(self._graph, "input_schema"):
109
- return cast(dict[str, Any], self._graph.input_schema)
110
- return {}
111
-
112
- async def cleanup(self):
113
- """
114
- Clean up resources when done with the graph.
115
- This should be called when the graph is no longer needed.
116
- """
117
- if hasattr(self, "_context_manager") and self._context_manager:
118
- try:
119
- await self._context_manager.__aexit__(None, None, None)
120
- except Exception as e:
121
- logger.warning(f"Error during context cleanup: {str(e)}")
122
- finally:
123
- self._context_manager = None
124
- self._graph = None
125
-
126
-
127
- class LangGraphConfig:
128
- def __init__(self, config_path: str = "langgraph.json"):
129
- self.config_path = config_path
130
- self._config: Optional[Dict[str, Any]] = None
131
- self._graphs: List[GraphConfig] = []
132
-
133
- @property
134
- def exists(self) -> bool:
135
- """Check if langgraph.json exists"""
136
- return os.path.exists(self.config_path)
137
-
138
- def load_config(self) -> Dict[str, Any]:
139
- """Load and validate langgraph configuration"""
140
- if not self.exists:
141
- raise FileNotFoundError(f"Config file not found: {self.config_path}")
142
-
143
- try:
144
- with open(self.config_path, "r") as f:
145
- config = json.load(f)
146
-
147
- required_fields = ["graphs"]
148
- missing_fields = [field for field in required_fields if field not in config]
149
- if missing_fields:
150
- raise ValueError(
151
- f"Missing required fields in langgraph.json: {missing_fields}"
152
- )
153
-
154
- if env_file := config.get("env"):
155
- env_path = os.path.abspath(os.path.normpath(env_file))
156
- if os.path.exists(env_path):
157
- if not load_dotenv(env_path):
158
- # log warning only if dotenv is not empty
159
- if os.path.getsize(env_path) > 0:
160
- logger.warning(
161
- f"Could not load environment variables from {env_path}"
162
- )
163
- else:
164
- logger.debug(f"Loaded environment variables from {env_path}")
165
-
166
- self._config = config
167
- self._load_graphs()
168
- return config
169
- except Exception as e:
170
- logger.error(f"Failed to load langgraph.json: {str(e)}")
171
- raise
172
-
173
- def _load_graphs(self):
174
- """Load all graph configurations"""
175
- if not self._config:
176
- return
177
-
178
- self._graphs = [
179
- GraphConfig.from_config(name, path)
180
- for name, path in self._config["graphs"].items()
181
- ]
182
-
183
- @property
184
- def graphs(self) -> List[GraphConfig]:
185
- """Get all graph configurations"""
186
- if not self._graphs:
187
- self.load_config()
188
- return self._graphs
189
-
190
- def get_graph(self, name: str) -> Optional[GraphConfig]:
191
- """Get a specific graph configuration by name"""
192
- return next((g for g in self.graphs if g.name == name), None)
193
-
194
- @property
195
- def dependencies(self) -> List[str]:
196
- """Get project dependencies"""
197
- return self._config.get("dependencies", []) if self._config else []
198
-
199
- @property
200
- def env_file(self) -> Optional[str]:
201
- """Get environment file path"""
202
- return self._config.get("env") if self._config else None
@@ -1,80 +0,0 @@
1
- import asyncio
2
- from os import environ as env
3
- from typing import Optional
4
-
5
- from dotenv import load_dotenv
6
- from uipath._cli._runtime._contracts import UiPathTraceContext
7
- from uipath._cli.middlewares import MiddlewareResult
8
-
9
- from ._runtime._context import LangGraphRuntimeContext
10
- from ._runtime._exception import LangGraphRuntimeError
11
- from ._runtime._runtime import LangGraphRuntime
12
- from ._utils._graph import LangGraphConfig
13
-
14
- load_dotenv()
15
-
16
-
17
- def langgraph_run_middleware(
18
- entrypoint: Optional[str], input: Optional[str], resume: bool
19
- ) -> MiddlewareResult:
20
- """Middleware to handle langgraph execution"""
21
- config = LangGraphConfig()
22
- if not config.exists:
23
- return MiddlewareResult(
24
- should_continue=True
25
- ) # Continue with normal flow if no langgraph.json
26
-
27
- try:
28
- bool_map = {"true": True, "false": False}
29
- tracing = env.get("UIPATH_TRACING_ENABLED", True)
30
- if isinstance(tracing, str) and tracing.lower() in bool_map:
31
- tracing = bool_map[tracing.lower()]
32
-
33
- async def execute():
34
- context = LangGraphRuntimeContext.from_config(
35
- env.get("UIPATH_CONFIG_PATH", "uipath.json")
36
- )
37
- context.entrypoint = entrypoint
38
- context.input = input
39
- context.resume = resume
40
- context.langgraph_config = config
41
- context.logs_min_level = env.get("LOG_LEVEL", "INFO")
42
- context.job_id = env.get("UIPATH_JOB_KEY")
43
- context.trace_id = env.get("UIPATH_TRACE_ID")
44
- context.tracing_enabled = tracing
45
- context.trace_context = UiPathTraceContext(
46
- enabled=tracing,
47
- trace_id=env.get("UIPATH_TRACE_ID"),
48
- parent_span_id=env.get("UIPATH_PARENT_SPAN_ID"),
49
- root_span_id=env.get("UIPATH_ROOT_SPAN_ID"),
50
- job_id=env.get("UIPATH_JOB_KEY"),
51
- org_id=env.get("UIPATH_ORGANIZATION_ID"),
52
- tenant_id=env.get("UIPATH_TENANT_ID"),
53
- process_key=env.get("UIPATH_PROCESS_UUID"),
54
- folder_key=env.get("UIPATH_FOLDER_KEY"),
55
- )
56
- context.langsmith_tracing_enabled = env.get("LANGSMITH_TRACING", False)
57
-
58
- # Add default env variables
59
- env["UIPATH_REQUESTING_PRODUCT"] = "uipath-python-sdk"
60
- env["UIPATH_REQUESTING_FEATURE"] = "langgraph-agent"
61
-
62
- async with LangGraphRuntime.from_context(context) as runtime:
63
- await runtime.execute()
64
-
65
- asyncio.run(execute())
66
-
67
- return MiddlewareResult(should_continue=False, error_message=None)
68
-
69
- except LangGraphRuntimeError as e:
70
- return MiddlewareResult(
71
- should_continue=False,
72
- error_message=e.error_info.detail,
73
- should_include_stacktrace=True,
74
- )
75
- except Exception as e:
76
- return MiddlewareResult(
77
- should_continue=False,
78
- error_message=f"Error: {str(e)}",
79
- should_include_stacktrace=True,
80
- )