Skip to content
Open
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
18 changes: 9 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[project]
name = "uipath-langchain"
version = "0.18.31"
version = "0.18.32"
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"
dependencies = [
"uipath>=2.14.35, <2.15.0",
"uipath>=2.14.36, <2.15.0",
"uipath-core>=0.5.33, <0.6.0",
"uipath-platform>=0.2.34, <0.3.0",
"uipath-runtime>=0.13.0, <0.14.0",
"uipath-llm-client>=1.21.0, <1.22.0",
"uipath-llm-client>=1.21.1, <1.22.0",
"langgraph>=1.2.11, <2.0.0",
"langchain-core>=1.6.1, <2.0.0",
"langgraph-checkpoint-sqlite>=3.1.1, <4.0.0",
Expand All @@ -28,7 +28,7 @@ dependencies = [
"pillow>=12.3.0, <13.0.0",
"rdflib>=7.0.0, <8.0.0",
"a2a-sdk>=1.1.2,<2.0.0",
"uipath-langchain-client[openai]>=1.21.0, <1.22.0",
"uipath-langchain-client[openai]>=1.21.1, <1.22.0",
]

classifiers = [
Expand All @@ -45,23 +45,23 @@ maintainers = [

[project.optional-dependencies]
anthropic = [
"uipath-langchain-client[anthropic]>=1.21.0, <1.22.0",
"uipath-langchain-client[anthropic]>=1.21.1, <1.22.0",
]
vertex = [
"uipath-langchain-client[google]>=1.21.0, <1.22.0",
"uipath-langchain-client[google]>=1.21.1, <1.22.0",
]
bedrock = [
"uipath-langchain-client[bedrock]>=1.21.0, <1.22.0",
"uipath-langchain-client[bedrock]>=1.21.1, <1.22.0",
"boto3-stubs>=1.41.4",
]
fireworks = [
"uipath-langchain-client[fireworks]>=1.21.0, <1.22.0",
"uipath-langchain-client[fireworks]>=1.21.1, <1.22.0",
]
code-interpreter = [
"langchain-quickjs>=0.3.5, <0.4.0",
]
all = [
"uipath-langchain-client[all]>=1.21.0, <1.22.0",
"uipath-langchain-client[all]>=1.21.1, <1.22.0",
"uipath-langchain[code-interpreter]",
]

Expand Down
2 changes: 2 additions & 0 deletions src/uipath_langchain/agent/exceptions/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ def _classify(status_code: int, body: object) -> _Verdict:
"""
code, category, title, own_detail = _status_verdict(status_code, body)
gateway_detail = body.get("detail") if isinstance(body, dict) else None
if not isinstance(gateway_detail, str):
gateway_detail = None
return code, category, title, gateway_detail or own_detail


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,8 @@ def _provider_message(error: UiPathAPIError) -> str:
def _is_unknown_model_error(error: UiPathAPIError) -> bool:
"""Whether Jev rejected the call because the configured model does not exist.

TypeSafe answers 400 ``{"detail": {"message": "Unknown model: <name>"}}``; a
TypeSafe answers 400 ``{"detail": {"message": "Unknown model: <name>"}}`` and the
LLM Gateway 400 ``{"detail": {"message": "Unsupported model used. ..."}}``; a
FastAPI-style 422 on ``body.model`` is accepted too. The model comes from the
tool settings, so the LLM cannot fix it.
"""
Expand All @@ -648,7 +649,11 @@ def _is_unknown_model_error(error: UiPathAPIError) -> bool:
isinstance(item, dict) and list(item.get("loc") or [])[-1:] == ["model"]
for item in detail
)
return _provider_message(error).lower().startswith("unknown model")
return (
_provider_message(error)
.lower()
.startswith(("unknown model", "unsupported model"))
)


def _read_tool_input(
Expand Down
32 changes: 32 additions & 0 deletions tests/agent/test_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ def test_unclassified_4xx_remains_unknown(status_code: int):
}


# A raw vendor passthrough error: the gateway relays the vendor's message in an
# object detail, so it is vendor text, not first-party ProblemDetails text.
_RAW_VENDOR_BODY: dict[str, object] = {
"detail": {
"error_type": "api_usage_error",
"message": "Cannot classify state for jane.doe@example.com",
}
}


@pytest.mark.parametrize(
"err_factory",
[
Expand All @@ -266,6 +276,7 @@ def test_unclassified_4xx_remains_unknown(status_code: int):
lambda: _api_error(400, {"message": "Malformed input request."}),
id="bedrock-envelope",
),
pytest.param(lambda: _api_error(400, _RAW_VENDOR_BODY), id="raw-vendor-detail"),
pytest.param(lambda: _api_error_text(400, _EDGE_HTML), id="raw-html"),
pytest.param(lambda: _api_error(400, {}), id="empty-body"),
],
Expand Down Expand Up @@ -297,6 +308,27 @@ def test_400_does_not_quote_the_provider_body(err_factory):
assert "doctype" not in rendered.lower()


@pytest.mark.parametrize(
("status_code", "expected_detail"),
[
pytest.param(400, None, id="400-canned"),
pytest.param(500, "Internal Server Error", id="500-reason-phrase"),
],
)
def test_raw_vendor_detail_is_never_displayed(
status_code: int, expected_detail: str | None
) -> None:
# An object detail relays the vendor's message, which may carry customer PII.
# It must not crash the mapper (it is not a string) nor reach the user.
error = _raise(_api_error(status_code, _RAW_VENDOR_BODY))

if expected_detail is not None:
# SYSTEM errors carry the generic prefix; the reason phrase follows it.
assert error.error_info.detail.endswith(expected_detail)
for rendered in (error.error_info.detail, str(error), repr(error)):
assert "jane.doe" not in rendered


def test_400_prefers_the_gateway_detail_over_the_canned_text():
"""A ProblemDetails ``detail`` is first-party UiPath text and more specific."""
err = _api_error(400, {"status": 400, "detail": "Model not enabled."})
Expand Down
11 changes: 11 additions & 0 deletions tests/agent/tools/internal_tools/test_jev_classifier_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,17 @@ def _jev_error(status: int, body: Any) -> UiPathAPIError:
},
id="typesafe-400",
),
pytest.param(
400,
{
"detail": {
"error_type": None,
"message": "Unsupported model used. The list of supported "
"models is : gpt-4o,jev-1.13.0",
}
},
id="llm-gateway-400",
),
pytest.param(
422,
{"detail": [{"loc": ["body", "model"], "msg": "Unknown model"}]},
Expand Down
62 changes: 31 additions & 31 deletions uv.lock

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

Loading