Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
36 changes: 31 additions & 5 deletions src/uipath_langchain/_cli/_runtime/_exception.py
Original file line number Diff line number Diff line change
@@ -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"
)
22 changes: 10 additions & 12 deletions src/uipath_langchain/_cli/_runtime/_graph_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/uipath_langchain/_cli/_runtime/_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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,
Expand Down
13 changes: 5 additions & 8 deletions src/uipath_langchain/_cli/_runtime/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 12 additions & 6 deletions src/uipath_langchain/_cli/_runtime/_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
UiPathBaseRuntime,
UiPathBreakpointResult,
UiPathErrorCategory,
UiPathErrorCode,
UiPathResumeTrigger,
UiPathRuntimeResult,
UiPathRuntimeStatus,
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
):
Expand Down Expand Up @@ -426,30 +432,30 @@ 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,
)

if isinstance(e, InvalidUpdateError):
return LangGraphRuntimeError(
"GRAPH_INVALID_UPDATE",
LangGraphErrorCode.GRAPH_INVALID_UPDATE,
str(e),
detail,
UiPathErrorCategory.USER,
)

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,
Expand Down
7 changes: 5 additions & 2 deletions src/uipath_langchain/_utils/_request_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 4 additions & 1 deletion src/uipath_langchain/agent/react/terminate_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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(
Expand Down
12 changes: 6 additions & 6 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.