diff --git a/pyproject.toml b/pyproject.toml index e2f954615..07a9d837e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-langchain" -version = "0.13.12" +version = "0.13.13" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" @@ -24,7 +24,7 @@ dependencies = [ "langchain-mcp-adapters==0.2.1", "pillow>=12.1.1", "a2a-sdk>=0.2.0,<1.0.0", - "uipath-langchain-client[openai]>=1.14.1,<1.15.0", + "uipath-langchain-client[openai]>=1.15.0,<1.16.0", ] classifiers = [ @@ -41,21 +41,21 @@ maintainers = [ [project.optional-dependencies] anthropic = [ - "uipath-langchain-client[anthropic]>=1.14.1,<1.15.0", + "uipath-langchain-client[anthropic]>=1.15.0,<1.16.0", ] vertex = [ - "uipath-langchain-client[google]>=1.14.1,<1.15.0", - "uipath-langchain-client[vertexai]>=1.14.1,<1.15.0", + "uipath-langchain-client[google]>=1.15.0,<1.16.0", + "uipath-langchain-client[vertexai]>=1.15.0,<1.16.0", ] bedrock = [ - "uipath-langchain-client[bedrock]>=1.14.1,<1.15.0", + "uipath-langchain-client[bedrock]>=1.15.0,<1.16.0", "boto3-stubs>=1.41.4", ] fireworks = [ - "uipath-langchain-client[fireworks]>=1.14.1,<1.15.0", + "uipath-langchain-client[fireworks]>=1.15.0,<1.16.0", ] all = [ - "uipath-langchain-client[all]>=1.14.1,<1.15.0", + "uipath-langchain-client[all]>=1.15.0,<1.16.0", ] [project.entry-points."uipath.middlewares"] diff --git a/src/uipath_langchain/agent/exceptions/licensing.py b/src/uipath_langchain/agent/exceptions/licensing.py index 229b3c8fd..be338bc23 100644 --- a/src/uipath_langchain/agent/exceptions/licensing.py +++ b/src/uipath_langchain/agent/exceptions/licensing.py @@ -1,18 +1,20 @@ -"""Convert LLM provider HTTP errors into structured AgentRuntimeErrors. +"""Map a normalized LLM-client ``UiPathAPIError`` into an ``AgentRuntimeError``. -Provider exceptions are first normalized to a common ``ProviderError`` (status_code + detail). -This module maps that status code to an AgentRuntimeError so -upstream handling (exception mapper, CAS bridge) can categorise by status code -without provider-specific logic. +The LLM client (uipath-llm-client / uipath-langchain-client) normalizes provider +HTTP errors into a ``UiPathAPIError`` carrying ``status_code`` and ``body``. This +module maps that status code to an ``AgentRuntimeError`` so upstream handling +(exception mapper, CAS bridge) can categorise without provider-specific logic. """ +from typing import NoReturn + +from uipath.llm_client import UiPathAPIError from uipath.runtime.errors import UiPathErrorCategory from uipath_langchain.agent.exceptions.exceptions import ( AgentRuntimeError, AgentRuntimeErrorCode, ) -from uipath_langchain.chat.provider_errors import extract_provider_error # Maps known LLM Gateway status codes to specific error codes. # Unknown status codes fall back to HTTP_ERROR. @@ -21,28 +23,25 @@ } -def raise_for_provider_http_error(e: BaseException) -> None: - """Re-raise provider-specific HTTP errors as a structured AgentRuntimeError. +def raise_for_provider_http_error(error: UiPathAPIError) -> NoReturn: + """Convert a normalized ``UiPathAPIError`` into a structured ``AgentRuntimeError``. - Extracts the HTTP status code and the gateway's ``detail`` - from any LLM provider exception and converts it to an - AgentRuntimeError. Does nothing if no HTTP status code can be extracted. + Reads the HTTP status code and the gateway's ``detail`` (from ``error.body``) + and re-raises as an ``AgentRuntimeError`` chained on the original. """ - err = extract_provider_error(e) - if err.status_code is None: - return - - code = _LLM_STATUS_CODE_MAP.get(err.status_code, AgentRuntimeErrorCode.HTTP_ERROR) + status_code = error.status_code + code = _LLM_STATUS_CODE_MAP.get(status_code, AgentRuntimeErrorCode.HTTP_ERROR) category = ( UiPathErrorCategory.DEPLOYMENT - if err.status_code == 403 + if status_code == 403 else UiPathErrorCategory.UNKNOWN ) + detail = error.body.get("detail") if isinstance(error.body, dict) else None raise AgentRuntimeError( code=code, - title=f"LLM provider returned HTTP {err.status_code}", - detail=err.detail or str(e), + title=f"LLM provider returned HTTP {status_code}", + detail=detail or error.message or str(error), category=category, - status=err.status_code, - ) from e + status=status_code, + ) from error diff --git a/src/uipath_langchain/agent/react/llm_node.py b/src/uipath_langchain/agent/react/llm_node.py index 32aecc788..4d945764d 100644 --- a/src/uipath_langchain/agent/react/llm_node.py +++ b/src/uipath_langchain/agent/react/llm_node.py @@ -11,6 +11,8 @@ from langchain_core.tools import BaseTool from pydantic import BaseModel from uipath.agent.react import RAISE_ERROR_TOOL +from uipath.llm_client import UiPathAPIError +from uipath.llm_client.utils.exceptions import as_uipath_error from uipath.runtime.errors import UiPathErrorCategory from uipath_langchain.chat.handlers import get_payload_handler @@ -119,11 +121,15 @@ async def llm_node(state: StateT): try: response = await llm.ainvoke(messages) - except Exception as e: - # LLM errors arrive as provider-specific exceptions (OpenAI, Bedrock, - # Vertex). Convert to a structured AgentRuntimeError with the HTTP - # status code so upstream handlers can categorise (e.g. 403 → licensing). + except UiPathAPIError as e: + # New LLM clients surface provider HTTP errors as a normalized UiPathAPIError directly. raise_for_provider_http_error(e) + except Exception as e: + # Legacy in-repo clients (use_new_llm_clients=False) raise raw provider SDK exceptions. + # Normalize via as_uipath_error and apply the same mapping when the error is HTTP-shaped; non-HTTP errors propagate. + uipath_error = as_uipath_error(e) + if isinstance(uipath_error, UiPathAPIError): + raise_for_provider_http_error(uipath_error) raise if not isinstance(response, AIMessage): raise AgentRuntimeError( diff --git a/src/uipath_langchain/chat/provider_errors.py b/src/uipath_langchain/chat/provider_errors.py deleted file mode 100644 index 070877894..000000000 --- a/src/uipath_langchain/chat/provider_errors.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Normalize LLM provider HTTP errors into a common shape. - -Providers behind the LLM Gateway each raise a different exception type, but all -carry the same gateway body (``{status, detail, ...}``); only the attribute that -holds it differs. LangChain may wrap the SDK error, so the useful fields can sit -a few links down the ``__cause__`` chain — always together, on one link. -""" - -from dataclasses import dataclass - - -@dataclass -class ProviderError: - """Normalized provider HTTP error: status code + user-facing detail.""" - - status_code: int | None = None - detail: str | None = None - - def __bool__(self) -> bool: - """Truthy once we have a status code — the signal that a provider matched.""" - return self.status_code is not None - - -def _int(value: object) -> int | None: - """The value if it is an int (a real HTTP status), else None. - - Guards against matching unrelated exceptions that happen to carry a - ``code``/``status_code`` attribute that isn't an HTTP status. - """ - return value if isinstance(value, int) else None - - -def _detail(body: object) -> str | None: - """The gateway ``detail`` message from a parsed body dict, if present.""" - if isinstance(body, dict): - return body.get("detail") - - return None - - -# One extractor per provider: read that SDK's status code and detail out of the exception, if present -# Returns an empty (falsy) ProviderError when ``e`` isn't that provider's error type - - -def _from_openai(e: BaseException) -> ProviderError: - """OpenAI / Anthropic: ``e.status_code`` + ``e.body``.""" - return ProviderError( - _int(getattr(e, "status_code", None)), _detail(getattr(e, "body", None)) - ) - - -def _from_vertex(e: BaseException) -> ProviderError: - """Vertex / google.genai ``APIError``.""" - return ProviderError( - _int(getattr(e, "code", None)), _detail(getattr(e, "details", None)) - ) - - -def _from_bedrock(e: BaseException) -> ProviderError: - """Bedrock — same ``e.status_code`` + ``e.body`` shape as OpenAI. - - Bedrock requests go through the uipath-client ``WrappedBotoClient`` shim - rather than boto3. On a gateway HTTP error its ``raise_for_status`` raises a - ``UiPathPermissionDeniedError`` (a ``UiPathAPIError`` / ``httpx.HTTPStatusError`` - subclass) that exposes the OpenAI-style ``.status_code`` and ``.body``. - """ - return ProviderError( - _int(getattr(e, "status_code", None)), _detail(getattr(e, "body", None)) - ) - - -def _from_botocore(e: BaseException) -> ProviderError: - """Bedrock via legacy direct boto3 (``use_new_llm_clients=False``): a - ``botocore.exceptions.ClientError`` carrying everything in ``e.response``.""" - resp = getattr(e, "response", None) - if not isinstance(resp, dict): - return ProviderError() - return ProviderError( - _int(resp.get("ResponseMetadata", {}).get("HTTPStatusCode")), - _detail(resp.get("Error")), - ) - - -_PROVIDERS = (_from_openai, _from_vertex, _from_bedrock, _from_botocore) - - -def extract_provider_error(e: BaseException | None) -> ProviderError: - """Return the first provider that matches ``e`` or any of its ``__cause__`` links.""" - if e is None: - return ProviderError() - for extract in _PROVIDERS: - error = extract(e) - if error: - return error - return extract_provider_error(e.__cause__) diff --git a/tests/agent/react/test_llm_node.py b/tests/agent/react/test_llm_node.py index b0a76e62b..338b86a44 100644 --- a/tests/agent/react/test_llm_node.py +++ b/tests/agent/react/test_llm_node.py @@ -3,6 +3,8 @@ from typing import Any from unittest.mock import AsyncMock, Mock +import httpx +import openai import pytest from langchain_core.language_models import BaseChatModel from langchain_core.messages import AIMessage, HumanMessage @@ -10,7 +12,12 @@ from langchain_core.tools import BaseTool from langchain_openai import AzureChatOpenAI from uipath.agent.react import END_EXECUTION_TOOL, RAISE_ERROR_TOOL +from uipath.llm_client import UiPathAPIError +from uipath_langchain.agent.exceptions.exceptions import ( + AgentRuntimeError, + AgentRuntimeErrorCode, +) from uipath_langchain.agent.react.llm_node import create_llm_node from uipath_langchain.agent.react.types import AgentGraphState @@ -342,3 +349,67 @@ async def test_multiple_raise_error_calls_keeps_only_first(self): assert len(response_message.tool_calls) == 1 assert response_message.tool_calls[0]["name"] == RAISE_ERROR_TOOL.name assert response_message.tool_calls[0]["args"]["message"] == "first error" + + +class TestLLMNodeProviderErrorHandling: + """llm_node maps provider HTTP errors to AgentRuntimeError (new + legacy clients).""" + + mock_model: Any + + def setup_method(self): + self.mock_model = _StubAzureChatOpenAI.model_construct() + self.mock_model.bind_tools = Mock(return_value=self.mock_model) + self.mock_model.bind = Mock(return_value=self.mock_model) + self.tool = Mock(spec=BaseTool) + self.tool.name = "regular_tool" + self.state = AgentGraphState(messages=[HumanMessage(content="Test")]) + + def _node_raising(self, exc: BaseException): + self.mock_model.ainvoke = AsyncMock(side_effect=exc) + return create_llm_node(self.mock_model, [self.tool]) + + @staticmethod + def _http_403() -> httpx.Response: + request = httpx.Request("POST", "http://gateway/") + return httpx.Response( + 403, request=request, json={"status": 403, "detail": "need AGU"} + ) + + @pytest.mark.asyncio + async def test_new_client_uipath_api_error_maps_to_license(self): + # New LLM clients raise a normalized UiPathAPIError -> except UiPathAPIError. + node = self._node_raising(UiPathAPIError.from_response(self._http_403())) + + with pytest.raises(AgentRuntimeError) as exc_info: + await node(self.state) + + info = exc_info.value.error_info + assert info.status == 403 + assert info.code.endswith(AgentRuntimeErrorCode.LICENSE_NOT_AVAILABLE.value) + assert info.detail == "need AGU" + + @pytest.mark.asyncio + async def test_legacy_raw_provider_error_is_normalized_and_mapped(self): + # Legacy clients (use_new_llm_clients=False) raise raw provider exceptions + # -> except Exception -> as_uipath_error normalizes -> mapped. + raw = openai.PermissionDeniedError( + "Forbidden", + response=self._http_403(), + body={"status": 403, "detail": "need AGU"}, + ) + node = self._node_raising(raw) + + with pytest.raises(AgentRuntimeError) as exc_info: + await node(self.state) + + info = exc_info.value.error_info + assert info.status == 403 + assert info.code.endswith(AgentRuntimeErrorCode.LICENSE_NOT_AVAILABLE.value) + + @pytest.mark.asyncio + async def test_non_http_error_propagates_unchanged(self): + # No HTTP status -> as_uipath_error yields a non-UiPathAPIError -> re-raised. + node = self._node_raising(ValueError("boom")) + + with pytest.raises(ValueError, match="boom"): + await node(self.state) diff --git a/tests/agent/test_licensing.py b/tests/agent/test_licensing.py new file mode 100644 index 000000000..8eb8870fa --- /dev/null +++ b/tests/agent/test_licensing.py @@ -0,0 +1,87 @@ +"""Tests for mapping a normalized ``UiPathAPIError`` to an ``AgentRuntimeError``. + +The LLM client normalizes provider HTTP errors into a ``UiPathAPIError`` carrying +``status_code`` and ``body``; ``raise_for_provider_http_error`` maps that onto the +agent's error taxonomy and surfaces the gateway ``detail``. +""" + +import httpx +import pytest +from uipath.llm_client import UiPathAPIError +from uipath.runtime.errors import UiPathErrorCategory + +from uipath_langchain.agent.exceptions.exceptions import ( + AgentRuntimeError, + AgentRuntimeErrorCode, +) +from uipath_langchain.agent.exceptions.licensing import raise_for_provider_http_error + +_DETAIL = "License not available for LLM usage. You need additional 'AGU'." + + +def _api_error(status_code: int, body: dict[str, object]) -> UiPathAPIError: + request = httpx.Request("POST", "http://gateway/") + response = httpx.Response(status_code, request=request, json=body) + return UiPathAPIError.from_response(response) + + +def test_403_maps_to_license_not_available(): + err = _api_error(403, {"status": 403, "detail": _DETAIL}) + with pytest.raises(AgentRuntimeError) as exc_info: + raise_for_provider_http_error(err) + + info = exc_info.value.error_info + assert info.status == 403 + assert info.category == UiPathErrorCategory.DEPLOYMENT + assert info.code.endswith(AgentRuntimeErrorCode.LICENSE_NOT_AVAILABLE.value) + assert info.detail == _DETAIL + + +def test_other_status_maps_to_http_error(): + err = _api_error(500, {"status": 500, "detail": "boom"}) + with pytest.raises(AgentRuntimeError) as exc_info: + raise_for_provider_http_error(err) + + info = exc_info.value.error_info + assert info.status == 500 + assert info.category == UiPathErrorCategory.UNKNOWN + assert info.code.endswith(AgentRuntimeErrorCode.HTTP_ERROR.value) + # UNKNOWN-category errors are wrapped with a generic prefix by AgentRuntimeError, + # but the original gateway detail is preserved within. + assert "boom" in info.detail + + +def test_legacy_raw_provider_error_is_normalized_and_mapped(): + # Legacy clients (use_new_llm_clients=False) raise raw provider SDK exceptions, + # not UiPathAPIError. as_uipath_error normalizes them so licensing still maps. + import openai + from uipath.llm_client.utils.exceptions import as_uipath_error + + request = httpx.Request("POST", "http://gateway/") + response = httpx.Response( + 403, request=request, json={"status": 403, "detail": _DETAIL} + ) + raw = openai.PermissionDeniedError( + "Forbidden", response=response, body={"status": 403, "detail": _DETAIL} + ) + + uipath_error = as_uipath_error(raw) + assert isinstance(uipath_error, UiPathAPIError) + with pytest.raises(AgentRuntimeError) as exc_info: + raise_for_provider_http_error(uipath_error) + + info = exc_info.value.error_info + assert info.status == 403 + assert info.code.endswith(AgentRuntimeErrorCode.LICENSE_NOT_AVAILABLE.value) + assert info.detail == _DETAIL + + +def test_detail_falls_back_when_body_has_none(): + # Body without a "detail" key -> fall back to the error message, not crash. + err = _api_error(403, {"status": 403}) + with pytest.raises(AgentRuntimeError) as exc_info: + raise_for_provider_http_error(err) + + info = exc_info.value.error_info + assert info.status == 403 + assert info.detail # non-empty (message / str fallback) diff --git a/tests/chat/test_provider_errors.py b/tests/chat/test_provider_errors.py deleted file mode 100644 index 9ca8810db..000000000 --- a/tests/chat/test_provider_errors.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Tests for normalizing provider HTTP errors into a common shape. - -Each provider behind the LLM Gateway raises a different exception type, but all -carry the same gateway body (``{status, detail, ...}``). ``extract_provider_error`` -reads the status code + detail off whichever attribute the SDK exposes, walking -the ``__cause__`` chain when LangChain wraps the SDK error. -""" - -import pytest -from uipath.runtime.errors import UiPathErrorCategory - -from uipath_langchain.agent.exceptions.exceptions import ( - AgentRuntimeError, - AgentRuntimeErrorCode, -) -from uipath_langchain.agent.exceptions.licensing import raise_for_provider_http_error -from uipath_langchain.chat.provider_errors import ( - ProviderError, - extract_provider_error, -) - -_DETAIL = "License not available for LLM usage. You need additional 'AGU'." -_BODY = { - "title": "License not available", - "status": 403, - "detail": _DETAIL, -} - - -class TestExtractProviderError: - def test_openai_status_code_and_body(self) -> None: - class OpenAIError(Exception): - status_code = 403 - body = _BODY - - result = extract_provider_error(OpenAIError("Forbidden")) - assert result == ProviderError(status_code=403, detail=_DETAIL) - - def test_bedrock_uipath_api_error_same_shape_as_openai(self) -> None: - # Bedrock via WrappedBotoClient surfaces as a UiPathAPIError (httpx - # subclass) exposing OpenAI-style .status_code / .body. - class UiPathPermissionDeniedError(Exception): - status_code = 403 - body = _BODY - - result = extract_provider_error(UiPathPermissionDeniedError("Forbidden")) - assert result == ProviderError(status_code=403, detail=_DETAIL) - - def test_vertex_wrapped_in_langchain_error(self) -> None: - # google.genai exposes .code + .details; LangChain wraps it in a class - # that itself exposes nothing, so the fields live on the __cause__. - class GenAIError(Exception): - code = 403 - details = _BODY - - class ChatGoogleGenerativeAIError(Exception): - pass - - try: - try: - raise GenAIError("403") - except GenAIError as cause: - raise ChatGoogleGenerativeAIError("wrapped") from cause - except ChatGoogleGenerativeAIError as wrapper: - result = extract_provider_error(wrapper) - - assert result == ProviderError(status_code=403, detail=_DETAIL) - - def test_botocore_response_dict(self) -> None: - # Legacy direct boto3 path: botocore.ClientError carries a response dict. - class ClientError(Exception): - response = { - "ResponseMetadata": {"HTTPStatusCode": 403}, - "Error": {"Code": "AccessDenied", "detail": _BODY["detail"]}, - } - - result = extract_provider_error(ClientError("denied")) - assert result.status_code == 403 - - def test_none_returns_empty(self) -> None: - result = extract_provider_error(None) - assert result == ProviderError() - assert not result - - def test_non_int_status_attribute_is_ignored(self) -> None: - # An unrelated exception that happens to carry a string `code` must not - # be mistaken for a provider HTTP error. - class OSLike(Exception): - code = "ENOENT" - - assert extract_provider_error(OSLike("nope")) == ProviderError() - - -class TestRaiseForProviderHttpError: - def test_403_maps_to_license_not_available(self) -> None: - class OpenAIError(Exception): - status_code = 403 - body = _BODY - - with pytest.raises(AgentRuntimeError) as exc_info: - raise_for_provider_http_error(OpenAIError("Forbidden")) - - info = exc_info.value.error_info - assert info.status == 403 - assert info.category == UiPathErrorCategory.DEPLOYMENT - assert info.code.endswith(AgentRuntimeErrorCode.LICENSE_NOT_AVAILABLE.value) - assert _DETAIL in info.detail - - def test_other_status_falls_back_to_http_error_and_str(self) -> None: - # Non-403 status, and no `detail` in the body → detail falls back to str(e). - class OpenAIError(Exception): - status_code = 500 - body: dict[str, str] = {} - - with pytest.raises(AgentRuntimeError) as exc_info: - raise_for_provider_http_error(OpenAIError("boom")) - - info = exc_info.value.error_info - assert info.status == 500 - assert info.category == UiPathErrorCategory.UNKNOWN - assert info.code.endswith(AgentRuntimeErrorCode.HTTP_ERROR.value) - assert "boom" in info.detail # str(e) fallback - - def test_no_status_does_not_raise(self) -> None: - # No extractable HTTP status → no-op (the original exception is left to - # propagate from the caller). Reaching the end without raising is the assert. - raise_for_provider_http_error(ValueError("unrelated transport error")) diff --git a/uv.lock b/uv.lock index 493ba6c5c..9dae8b5d7 100644 --- a/uv.lock +++ b/uv.lock @@ -4439,7 +4439,7 @@ wheels = [ [[package]] name = "uipath-langchain" -version = "0.13.12" +version = "0.13.13" source = { editable = "." } dependencies = [ { name = "a2a-sdk" }, @@ -4518,13 +4518,13 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "uipath", specifier = ">=2.11.14,<2.12.0" }, { name = "uipath-core", specifier = ">=0.5.20,<0.6.0" }, - { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["bedrock"], marker = "extra == 'bedrock'", specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["fireworks"], marker = "extra == 'fireworks'", specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["google"], marker = "extra == 'vertex'", specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["openai"], specifier = ">=1.14.1,<1.15.0" }, - { name = "uipath-langchain-client", extras = ["vertexai"], marker = "extra == 'vertex'", specifier = ">=1.14.1,<1.15.0" }, + { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["bedrock"], marker = "extra == 'bedrock'", specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["fireworks"], marker = "extra == 'fireworks'", specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["google"], marker = "extra == 'vertex'", specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["openai"], specifier = ">=1.15.0,<1.16.0" }, + { name = "uipath-langchain-client", extras = ["vertexai"], marker = "extra == 'vertex'", specifier = ">=1.15.0,<1.16.0" }, { name = "uipath-platform", specifier = ">=0.1.79,<0.2.0" }, { name = "uipath-runtime", specifier = ">=0.11.4,<0.12.0" }, ] @@ -4548,15 +4548,15 @@ dev = [ [[package]] name = "uipath-langchain-client" -version = "1.14.1" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain" }, { name = "uipath-llm-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/7d/e23a745eff9591068d8ee78c0de5b908bed0a9cb3a91b3efd9fba3b37577/uipath_langchain_client-1.14.1.tar.gz", hash = "sha256:c8840d4750f47d3cc85b3b84860330c4950eca2c6609e09114e2eadef83ecdb8", size = 38388, upload-time = "2026-06-23T10:52:45.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/30/bec71097faef75002100213eafea3951588f6dde6971cd61126b3df06882/uipath_langchain_client-1.15.0.tar.gz", hash = "sha256:8e2fcde6f1df76df7dd8b8bf2e3ddec3a5b443801a6f932dbb2dc78b56a91375", size = 38800, upload-time = "2026-06-26T14:37:51.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/a7/72ee48420f26bfb09ee13d5ec951065be40e54609feafc0a997b1af60ffd/uipath_langchain_client-1.14.1-py3-none-any.whl", hash = "sha256:fa85c798c99491819b8b4e6f06d7ba02d8ef848588892babb712f8e760867aae", size = 46794, upload-time = "2026-06-23T10:52:44.193Z" }, + { url = "https://files.pythonhosted.org/packages/d5/79/2373f32be11965fc7882e57d97bcc3ea9d929ae4a1484ac937189faf5f50/uipath_langchain_client-1.15.0-py3-none-any.whl", hash = "sha256:db13a464b56572aea5816608366dd45fbaaca096822dceac6fc283889835735e", size = 46854, upload-time = "2026-06-26T14:37:50.595Z" }, ] [package.optional-dependencies] @@ -4593,7 +4593,7 @@ vertexai = [ [[package]] name = "uipath-llm-client" -version = "1.14.0" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -4602,9 +4602,9 @@ dependencies = [ { name = "tenacity" }, { name = "uipath-platform" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/73/ee91ab5a3066ae96884dd6c19bda1609cfceb9592e1288b4fe69a81f4055/uipath_llm_client-1.14.0.tar.gz", hash = "sha256:6704f4f07b60ea5481f485016c1eff91686a106038f30b662116a842f5321c82", size = 12499974, upload-time = "2026-06-16T08:06:14.627Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/44/88c71ed3918014e80ea9507481266094f153b68c342244611f3a87c461a5/uipath_llm_client-1.15.0.tar.gz", hash = "sha256:d97015964cac9d50719559967e75436a63e04b2b2ebae82f5342b93394095ccc", size = 12509449, upload-time = "2026-06-26T14:36:05.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/71/34e87d62366b4b66fde6a961d39a352debecf0644a51e295afad0a0695aa/uipath_llm_client-1.14.0-py3-none-any.whl", hash = "sha256:5f09b9723185febf2e5a91ea42257b84df83cf3e105f2ae85291abcd4fa24074", size = 66103, upload-time = "2026-06-16T08:06:16.849Z" }, + { url = "https://files.pythonhosted.org/packages/a4/54/f890927bd2372f4e5f981259088c0e8ed564ca54d891e835123094187a67/uipath_llm_client-1.15.0-py3-none-any.whl", hash = "sha256:be2bf400c189c2cd3d855c1587d9c2937ee7c2f8d9b92ee3d57cea0dc617a770", size = 67904, upload-time = "2026-06-26T14:36:07.702Z" }, ] [[package]]