uipath-langchain 0.0.143__py3-none-any.whl → 0.0.145__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.

Potentially problematic release.


This version of uipath-langchain might be problematic. Click here for more details.

Files changed (33) hide show
  1. uipath_langchain/_cli/_runtime/_exception.py +31 -5
  2. uipath_langchain/_cli/_runtime/_graph_resolver.py +10 -12
  3. uipath_langchain/_cli/_runtime/_input.py +2 -2
  4. uipath_langchain/_cli/_runtime/_output.py +5 -8
  5. uipath_langchain/_cli/_runtime/_runtime.py +31 -6
  6. uipath_langchain/_cli/_utils/_schema.py +85 -0
  7. uipath_langchain/_cli/cli_eval.py +3 -9
  8. uipath_langchain/_cli/cli_init.py +3 -83
  9. uipath_langchain/_utils/_request_mixin.py +5 -2
  10. uipath_langchain/agent/react/__init__.py +12 -0
  11. uipath_langchain/agent/react/agent.py +76 -0
  12. uipath_langchain/agent/react/constants.py +2 -0
  13. uipath_langchain/agent/react/exceptions.py +11 -0
  14. uipath_langchain/agent/react/init_node.py +16 -0
  15. uipath_langchain/agent/react/llm_node.py +44 -0
  16. uipath_langchain/agent/react/router.py +97 -0
  17. uipath_langchain/agent/react/state.py +18 -0
  18. uipath_langchain/agent/react/terminate_node.py +53 -0
  19. uipath_langchain/agent/react/tools/__init__.py +7 -0
  20. uipath_langchain/agent/react/tools/tools.py +50 -0
  21. uipath_langchain/agent/react/utils.py +39 -0
  22. uipath_langchain/agent/tools/__init__.py +8 -0
  23. uipath_langchain/agent/tools/context_tool.py +42 -0
  24. uipath_langchain/agent/tools/process_tool.py +51 -0
  25. uipath_langchain/agent/tools/tool_factory.py +39 -0
  26. uipath_langchain/agent/tools/tool_node.py +22 -0
  27. uipath_langchain/agent/tools/utils.py +11 -0
  28. uipath_langchain/runtime_factories.py +21 -0
  29. {uipath_langchain-0.0.143.dist-info → uipath_langchain-0.0.145.dist-info}/METADATA +2 -2
  30. {uipath_langchain-0.0.143.dist-info → uipath_langchain-0.0.145.dist-info}/RECORD +33 -13
  31. {uipath_langchain-0.0.143.dist-info → uipath_langchain-0.0.145.dist-info}/WHEEL +0 -0
  32. {uipath_langchain-0.0.143.dist-info → uipath_langchain-0.0.145.dist-info}/entry_points.txt +0 -0
  33. {uipath_langchain-0.0.143.dist-info → uipath_langchain-0.0.145.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,43 @@
1
- from typing import Optional
1
+ from enum import Enum
2
+ from typing import Optional, Union
2
3
 
3
- from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathRuntimeError
4
+ from uipath._cli._runtime._contracts import (
5
+ UiPathBaseRuntimeError,
6
+ UiPathErrorCategory,
7
+ UiPathErrorCode,
8
+ )
4
9
 
5
10
 
6
- class LangGraphRuntimeError(UiPathRuntimeError):
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):
7
31
  """Custom exception for LangGraph runtime errors with structured error information."""
8
32
 
9
33
  def __init__(
10
34
  self,
11
- code: str,
35
+ code: Union[LangGraphErrorCode, UiPathErrorCode],
12
36
  title: str,
13
37
  detail: str,
14
38
  category: UiPathErrorCategory = UiPathErrorCategory.UNKNOWN,
15
39
  status: Optional[int] = None,
16
40
  ):
17
- super().__init__(code, title, detail, category, status, prefix="LANGGRAPH")
41
+ super().__init__(
42
+ code.value, title, detail, category, status, prefix="LANGGRAPH"
43
+ )
@@ -2,12 +2,10 @@ import asyncio
2
2
  from typing import Any, Awaitable, Callable, Optional
3
3
 
4
4
  from langgraph.graph.state import CompiledStateGraph, StateGraph
5
- from uipath._cli._runtime._contracts import (
6
- UiPathErrorCategory,
7
- )
5
+ from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathErrorCode
8
6
 
9
7
  from .._utils._graph import GraphConfig, LangGraphConfig
10
- from ._exception import LangGraphRuntimeError
8
+ from ._exception import LangGraphErrorCode, LangGraphRuntimeError
11
9
 
12
10
 
13
11
  class LangGraphJsonResolver:
@@ -36,7 +34,7 @@ class LangGraphJsonResolver:
36
34
  config = LangGraphConfig()
37
35
  if not config.exists:
38
36
  raise LangGraphRuntimeError(
39
- "CONFIG_MISSING",
37
+ LangGraphErrorCode.CONFIG_MISSING,
40
38
  "Invalid configuration",
41
39
  "Failed to load configuration",
42
40
  UiPathErrorCategory.DEPLOYMENT,
@@ -46,7 +44,7 @@ class LangGraphJsonResolver:
46
44
  config.load_config()
47
45
  except Exception as e:
48
46
  raise LangGraphRuntimeError(
49
- "CONFIG_INVALID",
47
+ LangGraphErrorCode.CONFIG_INVALID,
50
48
  "Invalid configuration",
51
49
  f"Failed to load configuration: {str(e)}",
52
50
  UiPathErrorCategory.DEPLOYMENT,
@@ -59,7 +57,7 @@ class LangGraphJsonResolver:
59
57
  elif not entrypoint:
60
58
  graph_names = ", ".join(g.name for g in graphs)
61
59
  raise LangGraphRuntimeError(
62
- "ENTRYPOINT_MISSING",
60
+ UiPathErrorCode.ENTRYPOINT_MISSING,
63
61
  "Entrypoint required",
64
62
  f"Multiple graphs available. Please specify one of: {graph_names}.",
65
63
  UiPathErrorCategory.DEPLOYMENT,
@@ -69,7 +67,7 @@ class LangGraphJsonResolver:
69
67
  self.graph_config = config.get_graph(entrypoint)
70
68
  if not self.graph_config:
71
69
  raise LangGraphRuntimeError(
72
- "GRAPH_NOT_FOUND",
70
+ LangGraphErrorCode.GRAPH_NOT_FOUND,
73
71
  "Graph not found",
74
72
  f"Graph '{entrypoint}' not found.",
75
73
  UiPathErrorCategory.DEPLOYMENT,
@@ -83,28 +81,28 @@ class LangGraphJsonResolver:
83
81
  )
84
82
  except ImportError as e:
85
83
  raise LangGraphRuntimeError(
86
- "GRAPH_IMPORT_ERROR",
84
+ LangGraphErrorCode.GRAPH_IMPORT_ERROR,
87
85
  "Graph import failed",
88
86
  f"Failed to import graph '{entrypoint}': {str(e)}",
89
87
  UiPathErrorCategory.USER,
90
88
  ) from e
91
89
  except TypeError as e:
92
90
  raise LangGraphRuntimeError(
93
- "GRAPH_TYPE_ERROR",
91
+ LangGraphErrorCode.GRAPH_TYPE_ERROR,
94
92
  "Invalid graph type",
95
93
  f"Graph '{entrypoint}' is not a valid StateGraph or CompiledStateGraph: {str(e)}",
96
94
  UiPathErrorCategory.USER,
97
95
  ) from e
98
96
  except ValueError as e:
99
97
  raise LangGraphRuntimeError(
100
- "GRAPH_VALUE_ERROR",
98
+ LangGraphErrorCode.GRAPH_VALUE_ERROR,
101
99
  "Invalid graph value",
102
100
  f"Invalid value in graph '{entrypoint}': {str(e)}",
103
101
  UiPathErrorCategory.USER,
104
102
  ) from e
105
103
  except Exception as e:
106
104
  raise LangGraphRuntimeError(
107
- "GRAPH_LOAD_ERROR",
105
+ LangGraphErrorCode.GRAPH_LOAD_ERROR,
108
106
  "Failed to load graph",
109
107
  f"Unexpected error loading graph '{entrypoint}': {str(e)}",
110
108
  UiPathErrorCategory.USER,
@@ -13,7 +13,7 @@ from uipath._cli._runtime._hitl import HitlReader
13
13
 
14
14
  from ._context import LangGraphRuntimeContext
15
15
  from ._conversation import uipath_to_human_messages
16
- from ._exception import LangGraphRuntimeError
16
+ from ._exception import LangGraphErrorCode, LangGraphRuntimeError
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -140,7 +140,7 @@ async def _get_latest_trigger(
140
140
  return cast(tuple[str, str, str, str, str], tuple(result))
141
141
  except Exception as e:
142
142
  raise LangGraphRuntimeError(
143
- "DB_QUERY_FAILED",
143
+ LangGraphErrorCode.DB_QUERY_FAILED,
144
144
  "Database query failed",
145
145
  f"Error querying resume trigger information: {str(e)}",
146
146
  UiPathErrorCategory.SYSTEM,
@@ -3,13 +3,10 @@ import logging
3
3
  from typing import Any
4
4
 
5
5
  from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
6
- from uipath._cli._runtime._contracts import (
7
- UiPathErrorCategory,
8
- UiPathResumeTrigger,
9
- )
6
+ from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathResumeTrigger
10
7
  from uipath._cli._runtime._hitl import HitlProcessor
11
8
 
12
- from ._exception import LangGraphRuntimeError
9
+ from ._exception import LangGraphErrorCode, LangGraphRuntimeError
13
10
 
14
11
  logger = logging.getLogger(__name__)
15
12
 
@@ -93,7 +90,7 @@ async def create_and_save_resume_trigger(
93
90
  """)
94
91
  except Exception as e:
95
92
  raise LangGraphRuntimeError(
96
- "DB_TABLE_CREATION_FAILED",
93
+ LangGraphErrorCode.DB_TABLE_CREATION_FAILED,
97
94
  "Failed to create resume triggers table",
98
95
  f"Database error while creating table: {str(e)}",
99
96
  UiPathErrorCategory.SYSTEM,
@@ -104,7 +101,7 @@ async def create_and_save_resume_trigger(
104
101
  resume_trigger = await hitl_processor.create_resume_trigger()
105
102
  except Exception as e:
106
103
  raise LangGraphRuntimeError(
107
- "HITL_EVENT_CREATION_FAILED",
104
+ LangGraphErrorCode.HITL_EVENT_CREATION_FAILED,
108
105
  "Failed to process HITL request",
109
106
  f"Error while trying to process HITL request: {str(e)}",
110
107
  UiPathErrorCategory.SYSTEM,
@@ -139,7 +136,7 @@ async def create_and_save_resume_trigger(
139
136
  await memory.conn.commit()
140
137
  except Exception as e:
141
138
  raise LangGraphRuntimeError(
142
- "DB_INSERT_FAILED",
139
+ LangGraphErrorCode.DB_INSERT_FAILED,
143
140
  "Failed to save resume trigger",
144
141
  f"Database error while saving resume trigger: {str(e)}",
145
142
  UiPathErrorCategory.SYSTEM,
@@ -2,28 +2,33 @@ import logging
2
2
  import os
3
3
  from contextlib import asynccontextmanager
4
4
  from typing import Any, AsyncGenerator, AsyncIterator, Optional, Sequence
5
+ from uuid import uuid4
5
6
 
6
7
  from langchain_core.runnables.config import RunnableConfig
7
8
  from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
8
9
  from langgraph.errors import EmptyInputError, GraphRecursionError, InvalidUpdateError
9
10
  from langgraph.graph.state import CompiledStateGraph, StateGraph
10
11
  from langgraph.types import Interrupt, StateSnapshot
12
+ from typing_extensions import override
11
13
  from uipath._cli._runtime._contracts import (
12
14
  UiPathBaseRuntime,
13
15
  UiPathBreakpointResult,
14
16
  UiPathErrorCategory,
17
+ UiPathErrorCode,
15
18
  UiPathResumeTrigger,
16
19
  UiPathRuntimeResult,
17
20
  UiPathRuntimeStatus,
18
21
  )
22
+ from uipath._cli.models.runtime_schema import Entrypoint
19
23
  from uipath._events._events import (
20
24
  UiPathAgentMessageEvent,
21
25
  UiPathAgentStateEvent,
22
26
  UiPathRuntimeEvent,
23
27
  )
24
28
 
29
+ from .._utils._schema import generate_schema_from_graph
25
30
  from ._context import LangGraphRuntimeContext
26
- from ._exception import LangGraphRuntimeError
31
+ from ._exception import LangGraphErrorCode, LangGraphRuntimeError
27
32
  from ._graph_resolver import AsyncResolver, LangGraphJsonResolver
28
33
  from ._input import get_graph_input
29
34
  from ._output import create_and_save_resume_trigger, serialize_output
@@ -79,7 +84,11 @@ class LangGraphRuntime(UiPathBaseRuntime):
79
84
  graph_config = self._get_graph_config()
80
85
 
81
86
  # Execute without streaming
82
- graph_output = await compiled_graph.ainvoke(graph_input, graph_config)
87
+ graph_output = await compiled_graph.ainvoke(
88
+ graph_input,
89
+ graph_config,
90
+ interrupt_before=self.context.breakpoints,
91
+ )
83
92
 
84
93
  # Get final state and create result
85
94
  self.context.result = await self._create_runtime_result(
@@ -140,6 +149,7 @@ class LangGraphRuntime(UiPathBaseRuntime):
140
149
  async for stream_chunk in compiled_graph.astream(
141
150
  graph_input,
142
151
  graph_config,
152
+ interrupt_before=self.context.breakpoints,
143
153
  stream_mode=["messages", "updates"],
144
154
  subgraphs=True,
145
155
  ):
@@ -426,7 +436,7 @@ class LangGraphRuntime(UiPathBaseRuntime):
426
436
 
427
437
  if isinstance(e, GraphRecursionError):
428
438
  return LangGraphRuntimeError(
429
- "GRAPH_RECURSION_ERROR",
439
+ LangGraphErrorCode.GRAPH_LOAD_ERROR,
430
440
  "Graph recursion limit exceeded",
431
441
  detail,
432
442
  UiPathErrorCategory.USER,
@@ -434,7 +444,7 @@ class LangGraphRuntime(UiPathBaseRuntime):
434
444
 
435
445
  if isinstance(e, InvalidUpdateError):
436
446
  return LangGraphRuntimeError(
437
- "GRAPH_INVALID_UPDATE",
447
+ LangGraphErrorCode.GRAPH_INVALID_UPDATE,
438
448
  str(e),
439
449
  detail,
440
450
  UiPathErrorCategory.USER,
@@ -442,14 +452,14 @@ class LangGraphRuntime(UiPathBaseRuntime):
442
452
 
443
453
  if isinstance(e, EmptyInputError):
444
454
  return LangGraphRuntimeError(
445
- "GRAPH_EMPTY_INPUT",
455
+ LangGraphErrorCode.GRAPH_EMPTY_INPUT,
446
456
  "The input data is empty",
447
457
  detail,
448
458
  UiPathErrorCategory.USER,
449
459
  )
450
460
 
451
461
  return LangGraphRuntimeError(
452
- "EXECUTION_ERROR",
462
+ UiPathErrorCode.EXECUTION_ERROR,
453
463
  "Graph execution failed",
454
464
  detail,
455
465
  UiPathErrorCategory.USER,
@@ -475,6 +485,21 @@ class LangGraphScriptRuntime(LangGraphRuntime):
475
485
  self.resolver = LangGraphJsonResolver(entrypoint=entrypoint)
476
486
  super().__init__(context, self.resolver)
477
487
 
488
+ @override
489
+ async def get_entrypoint(self) -> Entrypoint:
490
+ """Get entrypoint for this LangGraph runtime."""
491
+ graph = await self.resolver()
492
+ compiled_graph = graph.compile()
493
+ schema = generate_schema_from_graph(compiled_graph)
494
+
495
+ return Entrypoint(
496
+ file_path=self.context.entrypoint, # type: ignore[call-arg]
497
+ unique_id=str(uuid4()),
498
+ type="agent",
499
+ input=schema["input"],
500
+ output=schema["output"],
501
+ )
502
+
478
503
  async def cleanup(self) -> None:
479
504
  """Cleanup runtime resources including resolver."""
480
505
  await super().cleanup()
@@ -0,0 +1,85 @@
1
+ from typing import Any, Dict
2
+
3
+ from langgraph.graph.state import CompiledStateGraph
4
+
5
+
6
+ def resolve_refs(schema, root=None):
7
+ """Recursively resolves $ref references in a JSON schema."""
8
+ if root is None:
9
+ root = schema # Store the root schema to resolve $refs
10
+
11
+ if isinstance(schema, dict):
12
+ if "$ref" in schema:
13
+ ref_path = schema["$ref"].lstrip("#/").split("/")
14
+ ref_schema = root
15
+ for part in ref_path:
16
+ ref_schema = ref_schema.get(part, {})
17
+ return resolve_refs(ref_schema, root)
18
+
19
+ return {k: resolve_refs(v, root) for k, v in schema.items()}
20
+
21
+ elif isinstance(schema, list):
22
+ return [resolve_refs(item, root) for item in schema]
23
+
24
+ return schema
25
+
26
+
27
+ def process_nullable_types(
28
+ schema: Dict[str, Any] | list[Any] | Any,
29
+ ) -> Dict[str, Any] | list[Any]:
30
+ """Process the schema to handle nullable types by removing anyOf with null and keeping the base type."""
31
+ if isinstance(schema, dict):
32
+ if "anyOf" in schema and len(schema["anyOf"]) == 2:
33
+ types = [t.get("type") for t in schema["anyOf"]]
34
+ if "null" in types:
35
+ non_null_type = next(
36
+ t for t in schema["anyOf"] if t.get("type") != "null"
37
+ )
38
+ return non_null_type
39
+
40
+ return {k: process_nullable_types(v) for k, v in schema.items()}
41
+ elif isinstance(schema, list):
42
+ return [process_nullable_types(item) for item in schema]
43
+ return schema
44
+
45
+
46
+ def generate_schema_from_graph(
47
+ graph: CompiledStateGraph[Any, Any, Any],
48
+ ) -> Dict[str, Any]:
49
+ """Extract input/output schema from a LangGraph graph"""
50
+ schema = {
51
+ "input": {"type": "object", "properties": {}, "required": []},
52
+ "output": {"type": "object", "properties": {}, "required": []},
53
+ }
54
+
55
+ if hasattr(graph, "input_schema"):
56
+ if hasattr(graph.input_schema, "model_json_schema"):
57
+ input_schema = graph.input_schema.model_json_schema()
58
+ unpacked_ref_def_properties = resolve_refs(input_schema)
59
+
60
+ # Process the schema to handle nullable types
61
+ processed_properties = process_nullable_types(
62
+ unpacked_ref_def_properties.get("properties", {})
63
+ )
64
+
65
+ schema["input"]["properties"] = processed_properties
66
+ schema["input"]["required"] = unpacked_ref_def_properties.get(
67
+ "required", []
68
+ )
69
+
70
+ if hasattr(graph, "output_schema"):
71
+ if hasattr(graph.output_schema, "model_json_schema"):
72
+ output_schema = graph.output_schema.model_json_schema()
73
+ unpacked_ref_def_properties = resolve_refs(output_schema)
74
+
75
+ # Process the schema to handle nullable types
76
+ processed_properties = process_nullable_types(
77
+ unpacked_ref_def_properties.get("properties", {})
78
+ )
79
+
80
+ schema["output"]["properties"] = processed_properties
81
+ schema["output"]["required"] = unpacked_ref_def_properties.get(
82
+ "required", []
83
+ )
84
+
85
+ return schema
@@ -6,8 +6,9 @@ from openinference.instrumentation.langchain import (
6
6
  get_current_span,
7
7
  )
8
8
  from uipath._cli._evals._console_progress_reporter import ConsoleProgressReporter
9
+ from uipath._cli._evals._evaluate import evaluate
9
10
  from uipath._cli._evals._progress_reporter import StudioWebProgressReporter
10
- from uipath._cli._evals._runtime import UiPathEvalContext, UiPathEvalRuntime
11
+ from uipath._cli._evals._runtime import UiPathEvalContext
11
12
  from uipath._cli._runtime._contracts import (
12
13
  UiPathRuntimeFactory,
13
14
  )
@@ -82,14 +83,7 @@ def langgraph_eval_middleware(
82
83
 
83
84
  runtime_factory.add_instrumentor(LangChainInstrumentor, get_current_span)
84
85
 
85
- async def execute():
86
- async with UiPathEvalRuntime.from_eval_context(
87
- factory=runtime_factory, context=eval_context, event_bus=event_bus
88
- ) as eval_runtime:
89
- await eval_runtime.execute()
90
- await event_bus.wait_for_all()
91
-
92
- asyncio.run(execute())
86
+ asyncio.run(evaluate(runtime_factory, eval_context, event_bus))
93
87
  return MiddlewareResult(should_continue=False)
94
88
 
95
89
  except Exception as e:
@@ -6,7 +6,7 @@ import shutil
6
6
  import uuid
7
7
  from collections.abc import Generator
8
8
  from enum import Enum
9
- from typing import Any, Callable, Dict, overload
9
+ from typing import Any, Callable, overload
10
10
 
11
11
  import click
12
12
  from langgraph.graph.state import CompiledStateGraph
@@ -14,6 +14,8 @@ from uipath._cli._utils._console import ConsoleLogger
14
14
  from uipath._cli._utils._parse_ast import generate_bindings_json # type: ignore
15
15
  from uipath._cli.middlewares import MiddlewareResult
16
16
 
17
+ from uipath_langchain._cli._utils._schema import generate_schema_from_graph
18
+
17
19
  from ._utils._graph import LangGraphConfig
18
20
 
19
21
  console = ConsoleLogger()
@@ -27,88 +29,6 @@ class FileOperationStatus(str, Enum):
27
29
  SKIPPED = "skipped"
28
30
 
29
31
 
30
- def resolve_refs(schema, root=None):
31
- """Recursively resolves $ref references in a JSON schema."""
32
- if root is None:
33
- root = schema # Store the root schema to resolve $refs
34
-
35
- if isinstance(schema, dict):
36
- if "$ref" in schema:
37
- ref_path = schema["$ref"].lstrip("#/").split("/")
38
- ref_schema = root
39
- for part in ref_path:
40
- ref_schema = ref_schema.get(part, {})
41
- return resolve_refs(ref_schema, root)
42
-
43
- return {k: resolve_refs(v, root) for k, v in schema.items()}
44
-
45
- elif isinstance(schema, list):
46
- return [resolve_refs(item, root) for item in schema]
47
-
48
- return schema
49
-
50
-
51
- def process_nullable_types(
52
- schema: Dict[str, Any] | list[Any] | Any,
53
- ) -> Dict[str, Any] | list[Any]:
54
- """Process the schema to handle nullable types by removing anyOf with null and keeping the base type."""
55
- if isinstance(schema, dict):
56
- if "anyOf" in schema and len(schema["anyOf"]) == 2:
57
- types = [t.get("type") for t in schema["anyOf"]]
58
- if "null" in types:
59
- non_null_type = next(
60
- t for t in schema["anyOf"] if t.get("type") != "null"
61
- )
62
- return non_null_type
63
-
64
- return {k: process_nullable_types(v) for k, v in schema.items()}
65
- elif isinstance(schema, list):
66
- return [process_nullable_types(item) for item in schema]
67
- return schema
68
-
69
-
70
- def generate_schema_from_graph(
71
- graph: CompiledStateGraph[Any, Any, Any],
72
- ) -> Dict[str, Any]:
73
- """Extract input/output schema from a LangGraph graph"""
74
- schema = {
75
- "input": {"type": "object", "properties": {}, "required": []},
76
- "output": {"type": "object", "properties": {}, "required": []},
77
- }
78
-
79
- if hasattr(graph, "input_schema"):
80
- if hasattr(graph.input_schema, "model_json_schema"):
81
- input_schema = graph.input_schema.model_json_schema()
82
- unpacked_ref_def_properties = resolve_refs(input_schema)
83
-
84
- # Process the schema to handle nullable types
85
- processed_properties = process_nullable_types(
86
- unpacked_ref_def_properties.get("properties", {})
87
- )
88
-
89
- schema["input"]["properties"] = processed_properties
90
- schema["input"]["required"] = unpacked_ref_def_properties.get(
91
- "required", []
92
- )
93
-
94
- if hasattr(graph, "output_schema"):
95
- if hasattr(graph.output_schema, "model_json_schema"):
96
- output_schema = graph.output_schema.model_json_schema()
97
- unpacked_ref_def_properties = resolve_refs(output_schema)
98
-
99
- # Process the schema to handle nullable types
100
- processed_properties = process_nullable_types(
101
- unpacked_ref_def_properties.get("properties", {})
102
- )
103
-
104
- schema["output"]["properties"] = processed_properties
105
- schema["output"]["required"] = unpacked_ref_def_properties.get(
106
- "required", []
107
- )
108
-
109
- return schema
110
-
111
-
112
32
  def generate_agent_md_file(
113
33
  target_directory: str,
114
34
  file_name: str,
@@ -20,7 +20,10 @@ from tenacity import (
20
20
  from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathRuntimeError
21
21
  from uipath._utils._ssl_context import get_httpx_client_kwargs
22
22
 
23
- from uipath_langchain._cli._runtime._exception import LangGraphRuntimeError
23
+ from uipath_langchain._cli._runtime._exception import (
24
+ LangGraphErrorCode,
25
+ LangGraphRuntimeError,
26
+ )
24
27
  from uipath_langchain._utils._settings import (
25
28
  UiPathClientFactorySettings,
26
29
  UiPathClientSettings,
@@ -371,7 +374,7 @@ class UiPathRequestMixin(BaseModel):
371
374
  title = body.get("title", "").lower()
372
375
  if title == "license not available":
373
376
  raise LangGraphRuntimeError(
374
- code="LICENSE_NOT_AVAILABLE",
377
+ code=LangGraphErrorCode.LICENSE_NOT_AVAILABLE,
375
378
  title=body.get("title", "License Not Available"),
376
379
  detail=body.get(
377
380
  "detail", "License not available for this service"
@@ -0,0 +1,12 @@
1
+ """UiPath ReAct Agent implementation"""
2
+
3
+ from .agent import create_agent
4
+ from .state import AgentGraphNode, AgentGraphState
5
+ from .utils import resolve_output_model
6
+
7
+ __all__ = [
8
+ "create_agent",
9
+ "AgentGraphState",
10
+ "AgentGraphNode",
11
+ "resolve_output_model",
12
+ ]
@@ -0,0 +1,76 @@
1
+ import os
2
+ from typing import Sequence, Type
3
+
4
+ from langchain_core.language_models import BaseChatModel
5
+ from langchain_core.messages import HumanMessage, SystemMessage
6
+ from langchain_core.tools import BaseTool
7
+ from langgraph.constants import END, START
8
+ from langgraph.graph import StateGraph
9
+ from pydantic import BaseModel
10
+
11
+ from ..tools import create_tool_node
12
+ from .init_node import (
13
+ create_init_node,
14
+ )
15
+ from .llm_node import (
16
+ create_llm_node,
17
+ )
18
+ from .router import (
19
+ route_agent,
20
+ )
21
+ from .state import AgentGraphNode, AgentGraphState
22
+ from .terminate_node import (
23
+ create_terminate_node,
24
+ )
25
+ from .tools import create_flow_control_tools
26
+
27
+
28
+ def create_agent(
29
+ model: BaseChatModel,
30
+ tools: Sequence[BaseTool],
31
+ messages: Sequence[SystemMessage | HumanMessage],
32
+ *,
33
+ state_schema: Type[AgentGraphState] = AgentGraphState,
34
+ response_format: type[BaseModel] | None = None,
35
+ recursion_limit: int = 50,
36
+ ) -> StateGraph[AgentGraphState]:
37
+ """Build agent graph with INIT -> AGENT <-> TOOLS loop, terminated by control flow tools.
38
+
39
+ Control flow tools (end_execution, raise_error) are auto-injected alongside regular tools.
40
+ """
41
+ os.environ["LANGCHAIN_RECURSION_LIMIT"] = str(recursion_limit)
42
+
43
+ agent_tools = list(tools)
44
+ flow_control_tools: list[BaseTool] = create_flow_control_tools(response_format)
45
+ llm_tools: list[BaseTool] = [*agent_tools, *flow_control_tools]
46
+
47
+ init_node = create_init_node(messages)
48
+ agent_node = create_llm_node(model, llm_tools)
49
+ tool_nodes = create_tool_node(agent_tools)
50
+ terminate_node = create_terminate_node(response_format)
51
+
52
+ builder: StateGraph[AgentGraphState] = StateGraph(state_schema)
53
+ builder.add_node(AgentGraphNode.INIT, init_node)
54
+ builder.add_node(AgentGraphNode.AGENT, agent_node)
55
+
56
+ for tool_name, tool_node in tool_nodes.items():
57
+ builder.add_node(tool_name, tool_node)
58
+
59
+ builder.add_node(AgentGraphNode.TERMINATE, terminate_node)
60
+
61
+ builder.add_edge(START, AgentGraphNode.INIT)
62
+ builder.add_edge(AgentGraphNode.INIT, AgentGraphNode.AGENT)
63
+
64
+ tool_node_names = list(tool_nodes.keys())
65
+ builder.add_conditional_edges(
66
+ AgentGraphNode.AGENT,
67
+ route_agent,
68
+ [AgentGraphNode.AGENT, *tool_node_names, AgentGraphNode.TERMINATE],
69
+ )
70
+
71
+ for tool_name in tool_node_names:
72
+ builder.add_edge(tool_name, AgentGraphNode.AGENT)
73
+
74
+ builder.add_edge(AgentGraphNode.TERMINATE, END)
75
+
76
+ return builder
@@ -0,0 +1,2 @@
1
+ # Agent routing configuration
2
+ MAX_SUCCESSIVE_COMPLETIONS = 1
@@ -0,0 +1,11 @@
1
+ """Exceptions for the basic agent loop."""
2
+
3
+ from uipath._cli._runtime._contracts import UiPathRuntimeError
4
+
5
+
6
+ class AgentNodeRoutingException(Exception):
7
+ pass
8
+
9
+
10
+ class AgentTerminationException(UiPathRuntimeError):
11
+ pass
@@ -0,0 +1,16 @@
1
+ """State initialization node for the ReAct Agent graph."""
2
+
3
+ from typing import Sequence
4
+
5
+ from langchain_core.messages import HumanMessage, SystemMessage
6
+
7
+ from .state import AgentGraphState
8
+
9
+
10
+ def create_init_node(
11
+ messages: Sequence[SystemMessage | HumanMessage],
12
+ ):
13
+ def graph_state_init(_: AgentGraphState):
14
+ return {"messages": list(messages)}
15
+
16
+ return graph_state_init
@@ -0,0 +1,44 @@
1
+ """LLM node implementation for LangGraph."""
2
+
3
+ from typing import Sequence
4
+
5
+ from langchain_core.language_models import BaseChatModel
6
+ from langchain_core.messages import AIMessage, AnyMessage
7
+ from langchain_core.tools import BaseTool
8
+
9
+ from .constants import MAX_SUCCESSIVE_COMPLETIONS
10
+ from .state import AgentGraphState
11
+ from .utils import count_successive_completions
12
+
13
+
14
+ def create_llm_node(
15
+ model: BaseChatModel,
16
+ tools: Sequence[BaseTool] | None = None,
17
+ ):
18
+ """Invoke LLM with tools and dynamically control tool_choice based on successive completions.
19
+
20
+ When successive completions reach the limit, tool_choice is set to "required" to force
21
+ the LLM to use a tool and prevent infinite reasoning loops.
22
+ """
23
+ bindable_tools = list(tools) if tools else []
24
+ base_llm = model.bind_tools(bindable_tools) if bindable_tools else model
25
+
26
+ async def llm_node(state: AgentGraphState):
27
+ messages: list[AnyMessage] = state["messages"]
28
+
29
+ successive_completions = count_successive_completions(messages)
30
+
31
+ if successive_completions >= MAX_SUCCESSIVE_COMPLETIONS and bindable_tools:
32
+ llm = base_llm.bind(tool_choice="required")
33
+ else:
34
+ llm = base_llm
35
+
36
+ response = await llm.ainvoke(messages)
37
+ if not isinstance(response, AIMessage):
38
+ raise TypeError(
39
+ f"LLM returned {type(response).__name__} instead of AIMessage"
40
+ )
41
+
42
+ return {"messages": [response]}
43
+
44
+ return llm_node
@@ -0,0 +1,97 @@
1
+ """Routing functions for conditional edges in the agent graph."""
2
+
3
+ from typing import Literal
4
+
5
+ from langchain_core.messages import AIMessage, AnyMessage, ToolCall
6
+ from uipath.agent.react import END_EXECUTION_TOOL, RAISE_ERROR_TOOL
7
+
8
+ from .constants import MAX_SUCCESSIVE_COMPLETIONS
9
+ from .exceptions import AgentNodeRoutingException
10
+ from .state import AgentGraphNode, AgentGraphState
11
+ from .utils import count_successive_completions
12
+
13
+ FLOW_CONTROL_TOOLS = [END_EXECUTION_TOOL.name, RAISE_ERROR_TOOL.name]
14
+
15
+
16
+ def __filter_control_flow_tool_calls(
17
+ tool_calls: list[ToolCall],
18
+ ) -> list[ToolCall]:
19
+ """Remove control flow tools when multiple tool calls exist."""
20
+ if len(tool_calls) <= 1:
21
+ return tool_calls
22
+
23
+ return [tc for tc in tool_calls if tc.get("name") not in FLOW_CONTROL_TOOLS]
24
+
25
+
26
+ def __has_control_flow_tool(tool_calls: list[ToolCall]) -> bool:
27
+ """Check if any tool call is of a control flow tool."""
28
+ return any(tc.get("name") in FLOW_CONTROL_TOOLS for tc in tool_calls)
29
+
30
+
31
+ def __validate_last_message_is_AI(messages: list[AnyMessage]) -> AIMessage:
32
+ """Validate and return last message from state.
33
+
34
+ Raises:
35
+ AgentNodeRoutingException: If messages are empty or last message is not AIMessage
36
+ """
37
+ if not messages:
38
+ raise AgentNodeRoutingException(
39
+ "No messages in state - cannot route after agent"
40
+ )
41
+
42
+ last_message = messages[-1]
43
+ if not isinstance(last_message, AIMessage):
44
+ raise AgentNodeRoutingException(
45
+ f"Last message is not AIMessage (type: {type(last_message).__name__}) - cannot route after agent"
46
+ )
47
+
48
+ return last_message
49
+
50
+
51
+ def route_agent(
52
+ state: AgentGraphState,
53
+ ) -> list[str] | Literal[AgentGraphNode.AGENT, AgentGraphNode.TERMINATE]:
54
+ """Route after agent: handles all routing logic including control flow detection.
55
+
56
+ Routing logic:
57
+ 1. If multiple tool calls exist, filter out control flow tools (EndExecution, RaiseError)
58
+ 2. If control flow tool(s) remain, route to TERMINATE
59
+ 3. If regular tool calls remain, route to specific tool nodes (return list of tool names)
60
+ 4. If no tool calls, handle successive completions
61
+
62
+ Returns:
63
+ - list[str]: Tool node names for parallel execution
64
+ - AgentGraphNode.AGENT: For successive completions
65
+ - AgentGraphNode.TERMINATE: For control flow termination
66
+
67
+ Raises:
68
+ AgentNodeRoutingException: When encountering unexpected state (empty messages, non-AIMessage, or excessive completions)
69
+ """
70
+ messages = state.get("messages", [])
71
+ last_message = __validate_last_message_is_AI(messages)
72
+
73
+ tool_calls = list(last_message.tool_calls) if last_message.tool_calls else []
74
+ tool_calls = __filter_control_flow_tool_calls(tool_calls)
75
+
76
+ if tool_calls and __has_control_flow_tool(tool_calls):
77
+ return AgentGraphNode.TERMINATE
78
+
79
+ if tool_calls:
80
+ return [tc["name"] for tc in tool_calls]
81
+
82
+ successive_completions = count_successive_completions(messages)
83
+
84
+ if successive_completions > MAX_SUCCESSIVE_COMPLETIONS:
85
+ raise AgentNodeRoutingException(
86
+ f"Agent exceeded successive completions limit without producing tool calls "
87
+ f"(completions: {successive_completions}, max: {MAX_SUCCESSIVE_COMPLETIONS}). "
88
+ f"This should not happen as tool_choice='required' is enforced at the limit."
89
+ )
90
+
91
+ if last_message.content:
92
+ return AgentGraphNode.AGENT
93
+
94
+ raise AgentNodeRoutingException(
95
+ f"Agent produced empty response without tool calls "
96
+ f"(completions: {successive_completions}, has_content: False)"
97
+ )
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+
5
+ from langgraph.graph import MessagesState
6
+
7
+
8
+ class AgentGraphState(MessagesState):
9
+ """Agent Graph state for standard loop execution."""
10
+
11
+ pass
12
+
13
+
14
+ class AgentGraphNode(StrEnum):
15
+ INIT = "init"
16
+ AGENT = "agent"
17
+ TOOLS = "tools"
18
+ TERMINATE = "terminate"
@@ -0,0 +1,53 @@
1
+ """Termination node for the Agent graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from langchain_core.messages import AIMessage
6
+ from pydantic import BaseModel
7
+ from uipath._cli._runtime._contracts import UiPathErrorCode
8
+ from uipath.agent.react import END_EXECUTION_TOOL, RAISE_ERROR_TOOL
9
+
10
+ from .exceptions import (
11
+ AgentNodeRoutingException,
12
+ AgentTerminationException,
13
+ )
14
+ from .state import AgentGraphState
15
+
16
+
17
+ def create_terminate_node(
18
+ response_schema: type[BaseModel] | None = None,
19
+ ):
20
+ """Validates and returns end_execution args, or raises AgentTerminationException for raise_error."""
21
+
22
+ def terminate_node(state: AgentGraphState):
23
+ last_message = state["messages"][-1]
24
+ if not isinstance(last_message, AIMessage):
25
+ raise AgentNodeRoutingException(
26
+ f"Expected last message to be AIMessage, got {type(last_message).__name__}"
27
+ )
28
+
29
+ for tool_call in last_message.tool_calls:
30
+ tool_name = tool_call["name"]
31
+
32
+ if tool_name == END_EXECUTION_TOOL.name:
33
+ args = tool_call["args"]
34
+ output_schema = response_schema or END_EXECUTION_TOOL.args_schema
35
+ validated = output_schema.model_validate(args)
36
+ return validated.model_dump()
37
+
38
+ if tool_name == RAISE_ERROR_TOOL.name:
39
+ error_message = tool_call["args"].get(
40
+ "message", "The LLM did not set the error message"
41
+ )
42
+ detail = tool_call["args"].get("details", "")
43
+ raise AgentTerminationException(
44
+ code=UiPathErrorCode.EXECUTION_ERROR,
45
+ title=error_message,
46
+ detail=detail,
47
+ )
48
+
49
+ raise AgentNodeRoutingException(
50
+ "No control flow tool call found in terminate node. Unexpected state."
51
+ )
52
+
53
+ return terminate_node
@@ -0,0 +1,7 @@
1
+ from .tools import (
2
+ create_flow_control_tools,
3
+ )
4
+
5
+ __all__ = [
6
+ "create_flow_control_tools",
7
+ ]
@@ -0,0 +1,50 @@
1
+ """Control flow tools for agent execution."""
2
+
3
+ from typing import Any
4
+
5
+ from langchain_core.tools import BaseTool, StructuredTool
6
+ from pydantic import BaseModel
7
+ from uipath.agent.react import (
8
+ END_EXECUTION_TOOL,
9
+ RAISE_ERROR_TOOL,
10
+ )
11
+
12
+
13
+ def create_end_execution_tool(
14
+ agent_output_schema: type[BaseModel] | None = None,
15
+ ) -> StructuredTool:
16
+ """Never executed - routing intercepts and extracts args for successful termination."""
17
+ input_schema = agent_output_schema or END_EXECUTION_TOOL.args_schema
18
+
19
+ async def end_execution_fn(**kwargs: Any) -> dict[str, Any]:
20
+ return kwargs
21
+
22
+ return StructuredTool(
23
+ name=END_EXECUTION_TOOL.name,
24
+ description=END_EXECUTION_TOOL.description,
25
+ args_schema=input_schema,
26
+ coroutine=end_execution_fn,
27
+ )
28
+
29
+
30
+ def create_raise_error_tool() -> StructuredTool:
31
+ """Never executed - routing intercepts and raises AgentTerminationException."""
32
+
33
+ async def raise_error_fn(**kwargs: Any) -> dict[str, Any]:
34
+ return kwargs
35
+
36
+ return StructuredTool(
37
+ name=RAISE_ERROR_TOOL.name,
38
+ description=RAISE_ERROR_TOOL.description,
39
+ args_schema=RAISE_ERROR_TOOL.args_schema,
40
+ coroutine=raise_error_fn,
41
+ )
42
+
43
+
44
+ def create_flow_control_tools(
45
+ agent_output_schema: type[BaseModel] | None = None,
46
+ ) -> list[BaseTool]:
47
+ return [
48
+ create_end_execution_tool(agent_output_schema),
49
+ create_raise_error_tool(),
50
+ ]
@@ -0,0 +1,39 @@
1
+ """ReAct Agent loop utilities."""
2
+
3
+ from typing import Any, Sequence
4
+
5
+ from jsonschema_pydantic import jsonschema_to_pydantic # type: ignore[import-untyped]
6
+ from langchain_core.messages import AIMessage, BaseMessage
7
+ from pydantic import BaseModel
8
+ from uipath.agent.react import END_EXECUTION_TOOL
9
+
10
+
11
+ def resolve_output_model(
12
+ output_schema: dict[str, Any] | None,
13
+ ) -> type[BaseModel]:
14
+ """Fallback to default end_execution tool schema when no agent output schema is provided."""
15
+ if output_schema:
16
+ return jsonschema_to_pydantic(output_schema)
17
+
18
+ return END_EXECUTION_TOOL.args_schema
19
+
20
+
21
+ def count_successive_completions(messages: Sequence[BaseMessage]) -> int:
22
+ """Count consecutive AIMessages without tool calls at end of message history."""
23
+ if not messages:
24
+ return 0
25
+
26
+ count = 0
27
+ for message in reversed(messages):
28
+ if not isinstance(message, AIMessage):
29
+ break
30
+
31
+ if message.tool_calls:
32
+ break
33
+
34
+ if not message.content:
35
+ break
36
+
37
+ count += 1
38
+
39
+ return count
@@ -0,0 +1,8 @@
1
+ """Tool creation and management for LowCode agents."""
2
+
3
+ from .tool_factory import (
4
+ create_tools_from_resources,
5
+ )
6
+ from .tool_node import create_tool_node
7
+
8
+ __all__ = ["create_tools_from_resources", "create_tool_node"]
@@ -0,0 +1,42 @@
1
+ """Context tool creation for semantic index retrieval."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from langchain_core.tools import StructuredTool
8
+ from pydantic import BaseModel, Field
9
+ from uipath.agent.models.agent import AgentContextResourceConfig
10
+
11
+ from uipath_langchain.retrievers import ContextGroundingRetriever
12
+
13
+ from .utils import sanitize_tool_name
14
+
15
+
16
+ def create_context_tool(resource: AgentContextResourceConfig) -> StructuredTool:
17
+ tool_name = sanitize_tool_name(resource.name)
18
+ retriever = ContextGroundingRetriever(
19
+ index_name=resource.index_name,
20
+ folder_path=resource.folder_path,
21
+ number_of_results=resource.settings.result_count,
22
+ )
23
+
24
+ async def context_tool_fn(query: str) -> str:
25
+ docs = await retriever.ainvoke(query)
26
+
27
+ if not docs:
28
+ return ""
29
+
30
+ return json.dumps([doc.model_dump() for doc in docs], indent=2)
31
+
32
+ class ContextInputSchemaModel(BaseModel):
33
+ query: str = Field(
34
+ ..., description="The query to search for in the knowledge base"
35
+ )
36
+
37
+ return StructuredTool(
38
+ name=tool_name,
39
+ description=resource.description,
40
+ args_schema=ContextInputSchemaModel,
41
+ coroutine=context_tool_fn,
42
+ )
@@ -0,0 +1,51 @@
1
+ """Process tool creation for UiPath process execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Type
6
+
7
+ from jsonschema_pydantic import jsonschema_to_pydantic # type: ignore[import-untyped]
8
+ from langchain_core.tools import StructuredTool
9
+ from langgraph.types import interrupt
10
+ from pydantic import BaseModel
11
+ from uipath.agent.models.agent import AgentProcessToolResourceConfig
12
+ from uipath.models import InvokeProcess
13
+
14
+ from .utils import sanitize_tool_name
15
+
16
+
17
+ def create_process_tool(resource: AgentProcessToolResourceConfig) -> StructuredTool:
18
+ """Uses interrupt() to suspend graph execution until process completes (handled by runtime)."""
19
+ tool_name: str = sanitize_tool_name(resource.name)
20
+ process_name = resource.properties.process_name
21
+ folder_path = resource.properties.folder_path
22
+
23
+ input_model: Type[BaseModel] = jsonschema_to_pydantic(resource.input_schema)
24
+ output_model: Type[BaseModel] = jsonschema_to_pydantic(resource.output_schema)
25
+
26
+ async def process_tool_fn(**kwargs: Any):
27
+ try:
28
+ result = interrupt(
29
+ InvokeProcess(
30
+ name=process_name,
31
+ input_arguments=kwargs,
32
+ process_folder_path=folder_path,
33
+ process_folder_key=None,
34
+ )
35
+ )
36
+ except Exception:
37
+ raise
38
+
39
+ return result
40
+
41
+ class ProcessTool(StructuredTool):
42
+ """Process tool with OutputType for schema compatibility."""
43
+
44
+ OutputType: Type[BaseModel] = output_model
45
+
46
+ return ProcessTool(
47
+ name=tool_name,
48
+ description=resource.description,
49
+ args_schema=input_model,
50
+ coroutine=process_tool_fn,
51
+ )
@@ -0,0 +1,39 @@
1
+ """Factory functions for creating tools from agent resources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from langchain_core.tools import BaseTool, StructuredTool
6
+ from uipath.agent.models.agent import (
7
+ AgentContextResourceConfig,
8
+ AgentProcessToolResourceConfig,
9
+ BaseAgentResourceConfig,
10
+ LowCodeAgentDefinition,
11
+ )
12
+
13
+ from .context_tool import create_context_tool
14
+ from .process_tool import create_process_tool
15
+
16
+
17
+ async def create_tools_from_resources(
18
+ agent: LowCodeAgentDefinition,
19
+ ) -> list[BaseTool]:
20
+ tools: list[BaseTool] = []
21
+
22
+ for resource in agent.resources:
23
+ tool = await _build_tool_for_resource(resource)
24
+ if tool is not None:
25
+ tools.append(tool)
26
+
27
+ return tools
28
+
29
+
30
+ async def _build_tool_for_resource(
31
+ resource: BaseAgentResourceConfig,
32
+ ) -> StructuredTool | None:
33
+ if isinstance(resource, AgentProcessToolResourceConfig):
34
+ return create_process_tool(resource)
35
+
36
+ elif isinstance(resource, AgentContextResourceConfig):
37
+ return create_context_tool(resource)
38
+
39
+ return None
@@ -0,0 +1,22 @@
1
+ """Tool node factory wiring directly to LangGraph's ToolNode."""
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from langchain_core.tools import BaseTool
6
+ from langgraph.prebuilt import ToolNode
7
+
8
+
9
+ def create_tool_node(tools: Sequence[BaseTool]) -> dict[str, ToolNode]:
10
+ """Create individual ToolNode for each tool.
11
+
12
+ Args:
13
+ tools: Sequence of tools to create nodes for.
14
+
15
+ Returns:
16
+ Dict mapping tool.name -> ToolNode([tool]).
17
+ Each tool gets its own dedicated node for middleware composition.
18
+
19
+ Note:
20
+ handle_tool_errors=False delegates error handling to LangGraph's error boundary.
21
+ """
22
+ return {tool.name: ToolNode([tool], handle_tool_errors=False) for tool in tools}
@@ -0,0 +1,11 @@
1
+ """Tool-related utility functions."""
2
+
3
+ import re
4
+
5
+
6
+ def sanitize_tool_name(name: str) -> str:
7
+ """Sanitize tool name for LLM compatibility (alphanumeric, underscore, hyphen only, max 64 chars)."""
8
+ trim_whitespaces = "_".join(name.split())
9
+ sanitized_tool_name = re.sub(r"[^a-zA-Z0-9_-]", "", trim_whitespaces)
10
+ sanitized_tool_name = sanitized_tool_name[:64]
11
+ return sanitized_tool_name
@@ -0,0 +1,21 @@
1
+ """Runtime factory for LangGraph projects."""
2
+
3
+ from uipath._cli._runtime._contracts import UiPathRuntimeFactory
4
+
5
+ from ._cli._runtime._context import LangGraphRuntimeContext
6
+ from ._cli._runtime._runtime import LangGraphScriptRuntime
7
+
8
+
9
+ class LangGraphRuntimeFactory(
10
+ UiPathRuntimeFactory[LangGraphScriptRuntime, LangGraphRuntimeContext]
11
+ ):
12
+ """Factory for LangGraph runtimes."""
13
+
14
+ def __init__(self):
15
+ super().__init__(
16
+ LangGraphScriptRuntime,
17
+ LangGraphRuntimeContext,
18
+ context_generator=lambda **kwargs: LangGraphRuntimeContext.with_defaults(
19
+ **kwargs
20
+ ),
21
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath-langchain
3
- Version: 0.0.143
3
+ Version: 0.0.145
4
4
  Summary: UiPath Langchain
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-langchain-python
@@ -26,7 +26,7 @@ Requires-Dist: openai>=1.65.5
26
26
  Requires-Dist: openinference-instrumentation-langchain>=0.1.50
27
27
  Requires-Dist: pydantic-settings>=2.6.0
28
28
  Requires-Dist: python-dotenv>=1.0.1
29
- Requires-Dist: uipath<2.2.0,>=2.1.103
29
+ Requires-Dist: uipath<2.2.0,>=2.1.110
30
30
  Provides-Extra: langchain
31
31
  Description-Content-Type: text/markdown
32
32
 
@@ -1,32 +1,52 @@
1
1
  uipath_langchain/__init__.py,sha256=VBrvQn7d3nuOdN7zEnV2_S-uhmkjgEIlXiFVeZxZakQ,80
2
2
  uipath_langchain/middlewares.py,sha256=x3U_tmDIyMXPLzq6n-oNRAnpAF6pKa9wfkPYwE-oUfo,848
3
3
  uipath_langchain/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ uipath_langchain/runtime_factories.py,sha256=NFmEuXCraEfb2Sr02KQretYyVrtABUwnx5M68T6Z9Xo,644
4
5
  uipath_langchain/_cli/__init__.py,sha256=juqd9PbXs4yg45zMJ7BHAOPQjb7sgEbWE9InBtGZhfo,24
5
6
  uipath_langchain/_cli/cli_debug.py,sha256=zaB-W3_29FsCqF-YZ3EsayyxC957tg4tOjdcdX8ew-M,3311
6
7
  uipath_langchain/_cli/cli_dev.py,sha256=l3XFHrh-0OUFJq3zLMKuzedJAluGQBIZQTHP1KWOmpw,1725
7
- uipath_langchain/_cli/cli_eval.py,sha256=yzxOz-JOMMl1fejZNVQYlBSo-yUIxArtfp2EW1Ow6j4,3753
8
- uipath_langchain/_cli/cli_init.py,sha256=B-Ht1lz4HNlpYELZU7DLNhSrhGJbsaCdU9UMO2iHUgM,12654
8
+ uipath_langchain/_cli/cli_eval.py,sha256=Z1EYFObD0n-lfXfvjq4ejnrWQJrRrNktSxNPDE7QqSc,3529
9
+ uipath_langchain/_cli/cli_init.py,sha256=UAWDTHS-BCTYP5kOZh_GkN6gL19y-4UPBlCdcj_Izcg,9702
9
10
  uipath_langchain/_cli/cli_new.py,sha256=KKLxCzz7cDQ__rRr_a496IHWlSQXhmrBNgmKHnXAnTY,2336
10
11
  uipath_langchain/_cli/cli_run.py,sha256=DIsAKsbQ8gTRz44q9ZV3jBjrbM8bhS6lEQ3dd4joDFU,3712
11
12
  uipath_langchain/_cli/_runtime/_context.py,sha256=mjmGEogKiO8tUV878BgV9rFIeA9MCmEH6hgs5W_dm4g,328
12
13
  uipath_langchain/_cli/_runtime/_conversation.py,sha256=ayghRqhyLeVUZg1WHnpeOYtPNhRwDOl4z8OSYiJkWSU,11529
13
- uipath_langchain/_cli/_runtime/_exception.py,sha256=USKkLYkG-dzjX3fEiMMOHnVUpiXJs_xF0OQXCCOvbYM,546
14
- uipath_langchain/_cli/_runtime/_graph_resolver.py,sha256=5SmYr3KJ_Iy13QtN8XPOOmoSrdysDGlLsgfiebHDXfs,5296
15
- uipath_langchain/_cli/_runtime/_input.py,sha256=MTupo0p4F1vCTQi_KPZYJ2Um1X3QCmm8GeqC32HZbks,5724
16
- uipath_langchain/_cli/_runtime/_output.py,sha256=DOIxc9BKYtESOr9sLaBeppskWfUPse8B2PUDOFin0oU,4829
17
- uipath_langchain/_cli/_runtime/_runtime.py,sha256=ImDBluxga_WbIm9lnljjm7jGl1tZBPy5hUrXYOCcOIE,18262
14
+ uipath_langchain/_cli/_runtime/_exception.py,sha256=xHKeu8njByiMcObbggyZk0cXYXX5BjLLF9PtSJtB4_Q,1358
15
+ uipath_langchain/_cli/_runtime/_graph_resolver.py,sha256=c-JrsX7rx_CflDPfKhz9q-PgBrgI2IOBcYwiffwddh8,5457
16
+ uipath_langchain/_cli/_runtime/_input.py,sha256=HAJUxjNmOg9q7l_ebF1AzIKL5_ysXyjk1bWXHsjhEPI,5761
17
+ uipath_langchain/_cli/_runtime/_output.py,sha256=2VvdW4olv7Vd0c4grtTQazXxfBbcuocgSSP6V2P8uHE,4887
18
+ uipath_langchain/_cli/_runtime/_runtime.py,sha256=S8iGdSmBeMkM2m5QflBnuuVUi1L1hM0xE4UG1F872_I,19229
18
19
  uipath_langchain/_cli/_templates/langgraph.json.template,sha256=eeh391Gta_hoRgaNaZ58nW1LNvCVXA7hlAH6l7Veous,107
19
20
  uipath_langchain/_cli/_templates/main.py.template,sha256=GpSblGH2hwS9ibqQmX2iB2nsmOA5zDfEEF4ChLiMxbQ,875
20
21
  uipath_langchain/_cli/_utils/_graph.py,sha256=nMJWy8FmaD9rqPUY2lHc5uVpUzbXD1RO12uJnhe0kdo,6803
22
+ uipath_langchain/_cli/_utils/_schema.py,sha256=mO8Ic5mZr8jEqlNsStnXcmgKKvjChJxNTBO9vrMDOeE,3106
21
23
  uipath_langchain/_resources/AGENTS.md,sha256=5VmIfaQ6H91VxInnxFmJklURXeWIIQpGQTYBEmvvoVA,1060
22
24
  uipath_langchain/_resources/REQUIRED_STRUCTURE.md,sha256=BRmWWFtM0qNXj5uumALVxq9h6pifJDGh5NzuyctuH1Q,2569
23
25
  uipath_langchain/_tracing/__init__.py,sha256=C2dRvQ2ynxCmyICgE-rJHimWKEcFRME_o9gfX84Mb3Y,123
24
26
  uipath_langchain/_tracing/_instrument_traceable.py,sha256=8f9FyAKWE6kH1N8ErbpwqZHAzNjGwbLjQn7jdX5yAgA,4343
25
27
  uipath_langchain/_tracing/_utils.py,sha256=r_fiSk3HDDAcePY_UbbEYiSbNqzn5gFeMPYBDvGrFx0,902
26
28
  uipath_langchain/_utils/__init__.py,sha256=-w-4TD9ZnJDCpj4VIPXhJciukrmDJJbmnOFnhAkAaEU,81
27
- uipath_langchain/_utils/_request_mixin.py,sha256=sYvvn3_fUJxtF893xFpVGwJx2YoEbw1m5gp_U_lWjR8,20092
29
+ uipath_langchain/_utils/_request_mixin.py,sha256=_drxHTRpfyVn3g3ppKgn466EBaUWH83qyeGKLY41CGY,20142
28
30
  uipath_langchain/_utils/_settings.py,sha256=2fExMQJ88YptfldmzMfZIpsx-m1gfMkeYGf5t6KIe0A,3084
29
31
  uipath_langchain/_utils/_sleep_policy.py,sha256=e9pHdjmcCj4CVoFM1jMyZFelH11YatsgWfpyrfXzKBQ,1251
32
+ uipath_langchain/agent/react/__init__.py,sha256=rfVB6PQWUhPHff3J1BnPMBKBMaHfgEkII1gXwjiqUMY,272
33
+ uipath_langchain/agent/react/agent.py,sha256=cU9ZiXi7EfV1pMf9nZBO8LhjYUmRsncyBk9k1pEBRh8,2455
34
+ uipath_langchain/agent/react/constants.py,sha256=B2yqryh242DETslaRYacUPbVdpjvvApjsBira_qhQwk,61
35
+ uipath_langchain/agent/react/exceptions.py,sha256=b3lDhrIIHFljlLK3zXPznT7fYzfMRjSd8JfF4247tbI,226
36
+ uipath_langchain/agent/react/init_node.py,sha256=zfPKgxi_mWsX7nBcK6wpqcDjHx8Q61TSnXFcTPLUd28,389
37
+ uipath_langchain/agent/react/llm_node.py,sha256=jkbfzPNn6rNubgncPlPDQRNuk-sJbj08r95JfWxxWL8,1491
38
+ uipath_langchain/agent/react/router.py,sha256=Ttq5O1_8t-z7pQ9tGhiaMmd_Da7_TWULLcgOED7gw_A,3626
39
+ uipath_langchain/agent/react/state.py,sha256=EnkGXFlmMtJUy7BTZrbYlGBvvAZ70_HwKPW8n6uwjz0,330
40
+ uipath_langchain/agent/react/terminate_node.py,sha256=Uuc-0z4qcPjHB_qZlaEaM2mK1ymCuJJludS7LopyCZg,1898
41
+ uipath_langchain/agent/react/utils.py,sha256=0kZoEkGzddtTZSlGQcqbaPHH5MVtZegq0kBI5_vreGA,1060
42
+ uipath_langchain/agent/react/tools/__init__.py,sha256=LGfG8Dc32ffKdXQyMI2oYzhNnTs1wbzsddXz6eU-0MY,102
43
+ uipath_langchain/agent/react/tools/tools.py,sha256=vFBGnFrGocX__sotKisMJr2lxRRVqA0-uThzzhPADIw,1443
44
+ uipath_langchain/agent/tools/__init__.py,sha256=GqQZLoMxkujjsButdLruxgzmQLX9k0YPtBaBAULSV2o,222
45
+ uipath_langchain/agent/tools/context_tool.py,sha256=Xd2qvc52Ks2eFXF2n-i0d8QDnbHXls_V8jyyNXwdySI,1243
46
+ uipath_langchain/agent/tools/process_tool.py,sha256=3OPxkIAJw_haohDeyDwSo8CIz9300XSvqIz5yjItspk,1715
47
+ uipath_langchain/agent/tools/tool_factory.py,sha256=NfhUU7EWf-zkt4xglkzp8ReOYDvKmoOTiz-OBh3ACWs,1053
48
+ uipath_langchain/agent/tools/tool_node.py,sha256=TnXsjoShvhsoBuV5RoUVoJCc2zYPKSnJYSC9MGJoeOk,707
49
+ uipath_langchain/agent/tools/utils.py,sha256=DsFeZ7kDzFaZ0bGHQN6TlGMJ90wYr7P1Vo1rpHPHWws,401
30
50
  uipath_langchain/chat/__init__.py,sha256=WDcvy91ixvZ3Mq7Ae94g5CjyQwXovDBnEv1NlD5SXBE,116
31
51
  uipath_langchain/chat/models.py,sha256=PifcbDURqfttqVYKSnzdbOdbSiLiwHfQ6lWgVAtoLj8,16407
32
52
  uipath_langchain/embeddings/__init__.py,sha256=QICtYB58ZyqFfDQrEaO8lTEgAU5NuEKlR7iIrS0OBtc,156
@@ -37,8 +57,8 @@ uipath_langchain/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
37
57
  uipath_langchain/tools/preconfigured.py,sha256=SyvrLrM1kezZxVVytgScVO8nBfVYfFGobWjY7erzsYU,7490
38
58
  uipath_langchain/vectorstores/__init__.py,sha256=w8qs1P548ud1aIcVA_QhBgf_jZDrRMK5Lono78yA8cs,114
39
59
  uipath_langchain/vectorstores/context_grounding_vectorstore.py,sha256=TncIXG-YsUlO0R5ZYzWsM-Dj1SVCZbzmo2LraVxXelc,9559
40
- uipath_langchain-0.0.143.dist-info/METADATA,sha256=lh7djtB3vKXf2xB6b8BT9IksQGlun2fj2UiNFIver-E,4276
41
- uipath_langchain-0.0.143.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
- uipath_langchain-0.0.143.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
43
- uipath_langchain-0.0.143.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
44
- uipath_langchain-0.0.143.dist-info/RECORD,,
60
+ uipath_langchain-0.0.145.dist-info/METADATA,sha256=MiwJdi5I8Tc2FbpP3Qb2ICVn9Ykf3-BP7Z_LP6K2Bx4,4276
61
+ uipath_langchain-0.0.145.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
62
+ uipath_langchain-0.0.145.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
63
+ uipath_langchain-0.0.145.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
64
+ uipath_langchain-0.0.145.dist-info/RECORD,,