diff --git a/pyproject.toml b/pyproject.toml index dae659d..b4c44ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-langchain" -version = "0.0.143" +version = "0.0.144" description = "UiPath Langchain" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.10" @@ -111,4 +111,4 @@ asyncio_mode = "auto" name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" -explicit = true +explicit = true \ No newline at end of file diff --git a/src/uipath_langchain/_cli/_runtime/_exception.py b/src/uipath_langchain/_cli/_runtime/_exception.py index 2990be7..fc5e01a 100644 --- a/src/uipath_langchain/_cli/_runtime/_exception.py +++ b/src/uipath_langchain/_cli/_runtime/_exception.py @@ -1,17 +1,43 @@ -from typing import Optional +from enum import Enum +from typing import Optional, Union -from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathRuntimeError +from uipath._cli._runtime._contracts import ( + UiPathBaseRuntimeError, + UiPathErrorCategory, + UiPathErrorCode, +) -class LangGraphRuntimeError(UiPathRuntimeError): +class LangGraphErrorCode(Enum): + CONFIG_MISSING = "CONFIG_MISSING" + CONFIG_INVALID = "CONFIG_INVALID" + + GRAPH_NOT_FOUND = "GRAPH_NOT_FOUND" + GRAPH_IMPORT_ERROR = "GRAPH_IMPORT_ERROR" + GRAPH_TYPE_ERROR = "GRAPH_TYPE_ERROR" + GRAPH_VALUE_ERROR = "GRAPH_VALUE_ERROR" + GRAPH_LOAD_ERROR = "GRAPH_LOAD_ERROR" + GRAPH_INVALID_UPDATE = "GRAPH_INVALID_UPDATE" + GRAPH_EMPTY_INPUT = "GRAPH_EMPTY_INPUT" + + DB_QUERY_FAILED = "DB_QUERY_FAILED" + DB_TABLE_CREATION_FAILED = "DB_TABLE_CREATION_FAILED" + HITL_EVENT_CREATION_FAILED = "HITL_EVENT_CREATION_FAILED" + DB_INSERT_FAILED = "DB_INSERT_FAILED" + LICENSE_NOT_AVAILABLE = "LICENSE_NOT_AVAILABLE" + + +class LangGraphRuntimeError(UiPathBaseRuntimeError): """Custom exception for LangGraph runtime errors with structured error information.""" def __init__( self, - code: str, + code: Union[LangGraphErrorCode, UiPathErrorCode], title: str, detail: str, category: UiPathErrorCategory = UiPathErrorCategory.UNKNOWN, status: Optional[int] = None, ): - super().__init__(code, title, detail, category, status, prefix="LANGGRAPH") + super().__init__( + code.value, title, detail, category, status, prefix="LANGGRAPH" + ) diff --git a/src/uipath_langchain/_cli/_runtime/_graph_resolver.py b/src/uipath_langchain/_cli/_runtime/_graph_resolver.py index c1f7711..7019ceb 100644 --- a/src/uipath_langchain/_cli/_runtime/_graph_resolver.py +++ b/src/uipath_langchain/_cli/_runtime/_graph_resolver.py @@ -2,12 +2,10 @@ from typing import Any, Awaitable, Callable, Optional from langgraph.graph.state import CompiledStateGraph, StateGraph -from uipath._cli._runtime._contracts import ( - UiPathErrorCategory, -) +from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathErrorCode from .._utils._graph import GraphConfig, LangGraphConfig -from ._exception import LangGraphRuntimeError +from ._exception import LangGraphErrorCode, LangGraphRuntimeError class LangGraphJsonResolver: @@ -36,7 +34,7 @@ async def _resolve(self, entrypoint: Optional[str]) -> StateGraph[Any, Any, Any] config = LangGraphConfig() if not config.exists: raise LangGraphRuntimeError( - "CONFIG_MISSING", + LangGraphErrorCode.CONFIG_MISSING, "Invalid configuration", "Failed to load configuration", UiPathErrorCategory.DEPLOYMENT, @@ -46,7 +44,7 @@ async def _resolve(self, entrypoint: Optional[str]) -> StateGraph[Any, Any, Any] config.load_config() except Exception as e: raise LangGraphRuntimeError( - "CONFIG_INVALID", + LangGraphErrorCode.CONFIG_INVALID, "Invalid configuration", f"Failed to load configuration: {str(e)}", UiPathErrorCategory.DEPLOYMENT, @@ -59,7 +57,7 @@ async def _resolve(self, entrypoint: Optional[str]) -> StateGraph[Any, Any, Any] elif not entrypoint: graph_names = ", ".join(g.name for g in graphs) raise LangGraphRuntimeError( - "ENTRYPOINT_MISSING", + UiPathErrorCode.ENTRYPOINT_MISSING, "Entrypoint required", f"Multiple graphs available. Please specify one of: {graph_names}.", UiPathErrorCategory.DEPLOYMENT, @@ -69,7 +67,7 @@ async def _resolve(self, entrypoint: Optional[str]) -> StateGraph[Any, Any, Any] self.graph_config = config.get_graph(entrypoint) if not self.graph_config: raise LangGraphRuntimeError( - "GRAPH_NOT_FOUND", + LangGraphErrorCode.GRAPH_NOT_FOUND, "Graph not found", f"Graph '{entrypoint}' not found.", UiPathErrorCategory.DEPLOYMENT, @@ -83,28 +81,28 @@ async def _resolve(self, entrypoint: Optional[str]) -> StateGraph[Any, Any, Any] ) except ImportError as e: raise LangGraphRuntimeError( - "GRAPH_IMPORT_ERROR", + LangGraphErrorCode.GRAPH_IMPORT_ERROR, "Graph import failed", f"Failed to import graph '{entrypoint}': {str(e)}", UiPathErrorCategory.USER, ) from e except TypeError as e: raise LangGraphRuntimeError( - "GRAPH_TYPE_ERROR", + LangGraphErrorCode.GRAPH_TYPE_ERROR, "Invalid graph type", f"Graph '{entrypoint}' is not a valid StateGraph or CompiledStateGraph: {str(e)}", UiPathErrorCategory.USER, ) from e except ValueError as e: raise LangGraphRuntimeError( - "GRAPH_VALUE_ERROR", + LangGraphErrorCode.GRAPH_VALUE_ERROR, "Invalid graph value", f"Invalid value in graph '{entrypoint}': {str(e)}", UiPathErrorCategory.USER, ) from e except Exception as e: raise LangGraphRuntimeError( - "GRAPH_LOAD_ERROR", + LangGraphErrorCode.GRAPH_LOAD_ERROR, "Failed to load graph", f"Unexpected error loading graph '{entrypoint}': {str(e)}", UiPathErrorCategory.USER, diff --git a/src/uipath_langchain/_cli/_runtime/_input.py b/src/uipath_langchain/_cli/_runtime/_input.py index e568799..ad0b252 100644 --- a/src/uipath_langchain/_cli/_runtime/_input.py +++ b/src/uipath_langchain/_cli/_runtime/_input.py @@ -13,7 +13,7 @@ from ._context import LangGraphRuntimeContext from ._conversation import uipath_to_human_messages -from ._exception import LangGraphRuntimeError +from ._exception import LangGraphErrorCode, LangGraphRuntimeError logger = logging.getLogger(__name__) @@ -140,7 +140,7 @@ async def _get_latest_trigger( return cast(tuple[str, str, str, str, str], tuple(result)) except Exception as e: raise LangGraphRuntimeError( - "DB_QUERY_FAILED", + LangGraphErrorCode.DB_QUERY_FAILED, "Database query failed", f"Error querying resume trigger information: {str(e)}", UiPathErrorCategory.SYSTEM, diff --git a/src/uipath_langchain/_cli/_runtime/_output.py b/src/uipath_langchain/_cli/_runtime/_output.py index f16b088..7640ee6 100644 --- a/src/uipath_langchain/_cli/_runtime/_output.py +++ b/src/uipath_langchain/_cli/_runtime/_output.py @@ -3,13 +3,10 @@ from typing import Any from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver -from uipath._cli._runtime._contracts import ( - UiPathErrorCategory, - UiPathResumeTrigger, -) +from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathResumeTrigger from uipath._cli._runtime._hitl import HitlProcessor -from ._exception import LangGraphRuntimeError +from ._exception import LangGraphErrorCode, LangGraphRuntimeError logger = logging.getLogger(__name__) @@ -93,7 +90,7 @@ async def create_and_save_resume_trigger( """) except Exception as e: raise LangGraphRuntimeError( - "DB_TABLE_CREATION_FAILED", + LangGraphErrorCode.DB_TABLE_CREATION_FAILED, "Failed to create resume triggers table", f"Database error while creating table: {str(e)}", UiPathErrorCategory.SYSTEM, @@ -104,7 +101,7 @@ async def create_and_save_resume_trigger( resume_trigger = await hitl_processor.create_resume_trigger() except Exception as e: raise LangGraphRuntimeError( - "HITL_EVENT_CREATION_FAILED", + LangGraphErrorCode.HITL_EVENT_CREATION_FAILED, "Failed to process HITL request", f"Error while trying to process HITL request: {str(e)}", UiPathErrorCategory.SYSTEM, @@ -139,7 +136,7 @@ async def create_and_save_resume_trigger( await memory.conn.commit() except Exception as e: raise LangGraphRuntimeError( - "DB_INSERT_FAILED", + LangGraphErrorCode.DB_INSERT_FAILED, "Failed to save resume trigger", f"Database error while saving resume trigger: {str(e)}", UiPathErrorCategory.SYSTEM, diff --git a/src/uipath_langchain/_cli/_runtime/_runtime.py b/src/uipath_langchain/_cli/_runtime/_runtime.py index ac51354..7169f6b 100644 --- a/src/uipath_langchain/_cli/_runtime/_runtime.py +++ b/src/uipath_langchain/_cli/_runtime/_runtime.py @@ -12,6 +12,7 @@ UiPathBaseRuntime, UiPathBreakpointResult, UiPathErrorCategory, + UiPathErrorCode, UiPathResumeTrigger, UiPathRuntimeResult, UiPathRuntimeStatus, @@ -23,7 +24,7 @@ ) from ._context import LangGraphRuntimeContext -from ._exception import LangGraphRuntimeError +from ._exception import LangGraphErrorCode, LangGraphRuntimeError from ._graph_resolver import AsyncResolver, LangGraphJsonResolver from ._input import get_graph_input from ._output import create_and_save_resume_trigger, serialize_output @@ -79,7 +80,11 @@ async def execute(self) -> Optional[UiPathRuntimeResult]: graph_config = self._get_graph_config() # Execute without streaming - graph_output = await compiled_graph.ainvoke(graph_input, graph_config) + graph_output = await compiled_graph.ainvoke( + graph_input, + graph_config, + interrupt_before=self.context.breakpoints, + ) # Get final state and create result self.context.result = await self._create_runtime_result( @@ -140,6 +145,7 @@ async def stream( async for stream_chunk in compiled_graph.astream( graph_input, graph_config, + interrupt_before=self.context.breakpoints, stream_mode=["messages", "updates"], subgraphs=True, ): @@ -426,7 +432,7 @@ def _create_runtime_error(self, e: Exception) -> LangGraphRuntimeError: if isinstance(e, GraphRecursionError): return LangGraphRuntimeError( - "GRAPH_RECURSION_ERROR", + LangGraphErrorCode.GRAPH_LOAD_ERROR, "Graph recursion limit exceeded", detail, UiPathErrorCategory.USER, @@ -434,7 +440,7 @@ def _create_runtime_error(self, e: Exception) -> LangGraphRuntimeError: if isinstance(e, InvalidUpdateError): return LangGraphRuntimeError( - "GRAPH_INVALID_UPDATE", + LangGraphErrorCode.GRAPH_INVALID_UPDATE, str(e), detail, UiPathErrorCategory.USER, @@ -442,14 +448,14 @@ def _create_runtime_error(self, e: Exception) -> LangGraphRuntimeError: if isinstance(e, EmptyInputError): return LangGraphRuntimeError( - "GRAPH_EMPTY_INPUT", + LangGraphErrorCode.GRAPH_EMPTY_INPUT, "The input data is empty", detail, UiPathErrorCategory.USER, ) return LangGraphRuntimeError( - "EXECUTION_ERROR", + UiPathErrorCode.EXECUTION_ERROR, "Graph execution failed", detail, UiPathErrorCategory.USER, diff --git a/src/uipath_langchain/_utils/_request_mixin.py b/src/uipath_langchain/_utils/_request_mixin.py index af66004..8f0626c 100644 --- a/src/uipath_langchain/_utils/_request_mixin.py +++ b/src/uipath_langchain/_utils/_request_mixin.py @@ -20,7 +20,10 @@ from uipath._cli._runtime._contracts import UiPathErrorCategory, UiPathRuntimeError from uipath._utils._ssl_context import get_httpx_client_kwargs -from uipath_langchain._cli._runtime._exception import LangGraphRuntimeError +from uipath_langchain._cli._runtime._exception import ( + LangGraphErrorCode, + LangGraphRuntimeError, +) from uipath_langchain._utils._settings import ( UiPathClientFactorySettings, UiPathClientSettings, @@ -371,7 +374,7 @@ def _make_status_error( title = body.get("title", "").lower() if title == "license not available": raise LangGraphRuntimeError( - code="LICENSE_NOT_AVAILABLE", + code=LangGraphErrorCode.LICENSE_NOT_AVAILABLE, title=body.get("title", "License Not Available"), detail=body.get( "detail", "License not available for this service" diff --git a/src/uipath_langchain/agent/react/terminate_node.py b/src/uipath_langchain/agent/react/terminate_node.py index 6b1fccc..3408868 100644 --- a/src/uipath_langchain/agent/react/terminate_node.py +++ b/src/uipath_langchain/agent/react/terminate_node.py @@ -4,6 +4,7 @@ from langchain_core.messages import AIMessage from pydantic import BaseModel +from uipath._cli._runtime._contracts import UiPathErrorCode from uipath.agent.react import END_EXECUTION_TOOL, RAISE_ERROR_TOOL from .exceptions import ( @@ -40,7 +41,9 @@ def terminate_node(state: AgentGraphState): ) detail = tool_call["args"].get("details", "") raise AgentTerminationException( - code="400", title=error_message, detail=detail + code=UiPathErrorCode.EXECUTION_ERROR, + title=error_message, + detail=detail, ) raise AgentNodeRoutingException( diff --git a/uv.lock b/uv.lock index 3568b9e..49662ca 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.13'", @@ -3308,7 +3308,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.1.103" +version = "2.1.108" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-monitor-opentelemetry" }, @@ -3330,14 +3330,14 @@ dependencies = [ { name = "tomli" }, { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/1f/be5ff1529e081e0df196ce6834d96239a18baa41af4a48d7cd82975131d7/uipath-2.1.103.tar.gz", hash = "sha256:1f8f91f8d4b7afbdf42e07a4309153d954d221f52ce5cf9a51d5038f6f17195c", size = 2235813, upload-time = "2025-10-23T14:44:36.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/7a/b453ccd146c2aaaad87ff4cd71e59fdeec08ad380ade08cc4b980fa46602/uipath-2.1.108.tar.gz", hash = "sha256:f0a89c6ad4394dacae3d448488b6f6e03129afa3097cb2f2f8193599987afc3f", size = 2241996, upload-time = "2025-10-24T16:07:35.416Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/a8/5ac2a1ec22c84aaac7324e11f8d40a8a29207c04bcc4f800d5f6841dbc8f/uipath-2.1.103-py3-none-any.whl", hash = "sha256:db6afd143174fc757aa7edae6526dfdbf7090ff46ff2803909e4be3d9cf0dadb", size = 295943, upload-time = "2025-10-23T14:44:34.799Z" }, + { url = "https://files.pythonhosted.org/packages/dc/84/49efdb83e21795d9eabea2dcce80f33868597cc31f317ec13f389e410dd0/uipath-2.1.108-py3-none-any.whl", hash = "sha256:031ba5bfe1f9785e8b3a1ea792acad9ac92585b7e51051c11c5e7abe4a03ba25", size = 298633, upload-time = "2025-10-24T16:07:33.429Z" }, ] [[package]] name = "uipath-langchain" -version = "0.0.143" +version = "0.0.145" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -3383,7 +3383,7 @@ requires-dist = [ { name = "openinference-instrumentation-langchain", specifier = ">=0.1.50" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "uipath", specifier = ">=2.1.103,<2.2.0" }, + { name = "uipath", specifier = ">=2.1.107,<2.2.0" }, { name = "uipath-langchain", marker = "extra == 'langchain'", specifier = ">=0.0.2" }, ] provides-extras = ["langchain"]