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