diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb1d74..bdee1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `uipath_llm_client` (core package) will be documented in this file. +## [1.9.1] - 2026-04-17 + +### Added +- `utils.model_family.is_anthropic_model_name()` helper and `ANTHROPIC_MODEL_NAME_KEYWORDS` tuple — name-based Claude detection for BYOM deployments where discovery does not expose `modelFamily` + +### Fixed +- `UiPathLiteLLM` now detects Claude-family models by name when `modelFamily` is unavailable (BYOM), correctly routing Bedrock/Vertex provider selection and default flavors + ## [1.9.0] - 2026-04-17 ### Added diff --git a/packages/uipath_langchain_client/CHANGELOG.md b/packages/uipath_langchain_client/CHANGELOG.md index 7f03f5c..b26b1a5 100644 --- a/packages/uipath_langchain_client/CHANGELOG.md +++ b/packages/uipath_langchain_client/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `uipath_langchain_client` will be documented in this file. +## [1.9.1] - 2026-04-17 + +### Fixed +- Detect Anthropic-family models by additional name keywords (`anthropic`, `opus`, `sonnet`, `haiku`, `mythos`) alongside `claude` — applies to Bedrock INVOKE factory routing and the normalized client's empty tool-call content workaround. Uses the shared `is_anthropic_model_name()` helper from core 1.9.1. + +### Changed +- Minimum `uipath-llm-client` bumped to 1.9.1 for the shared `is_anthropic_model_name()` helper + ## [1.9.0] - 2026-04-17 ### Changed diff --git a/packages/uipath_langchain_client/pyproject.toml b/packages/uipath_langchain_client/pyproject.toml index fe516b2..edfc140 100644 --- a/packages/uipath_langchain_client/pyproject.toml +++ b/packages/uipath_langchain_client/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "langchain>=1.2.15", - "uipath-llm-client>=1.9.0", + "uipath-llm-client>=1.9.1", ] [project.optional-dependencies] diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py index 0876433..93fe019 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LangChain Client" __description__ = "A Python client for interacting with UiPath's LLM services via LangChain." -__version__ = "1.9.0" +__version__ = "1.9.1" diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py index a79025e..7e7badd 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py @@ -48,12 +48,12 @@ get_captured_response_headers, set_captured_response_headers, ) -from uipath.llm_client.utils.retry import RetryConfig from uipath_langchain_client.settings import ( UiPathAPIConfig, UiPathBaseSettings, get_default_client_settings, ) +from uipath_langchain_client.utils import RetryConfig class UiPathBaseLLMClient(BaseModel, ABC): diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/chat_models.py index 619106c..4bbc72b 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/chat_models.py @@ -22,9 +22,14 @@ from typing_extensions import Self from uipath.llm_client.clients.litellm import UiPathLiteLLM -from uipath.llm_client.settings.constants import ApiFlavor, ApiType, RoutingMode, VendorType from uipath_langchain_client.base_client import UiPathBaseChatModel -from uipath_langchain_client.settings import UiPathAPIConfig +from uipath_langchain_client.settings import ( + ApiFlavor, + ApiType, + RoutingMode, + UiPathAPIConfig, + VendorType, +) try: from langchain_litellm import ChatLiteLLM diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/embeddings.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/embeddings.py index 180d979..9648e22 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/embeddings.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/embeddings.py @@ -16,9 +16,14 @@ from typing_extensions import Self from uipath.llm_client.clients.litellm import UiPathLiteLLM -from uipath.llm_client.settings.constants import ApiFlavor, ApiType, RoutingMode, VendorType from uipath_langchain_client.base_client import UiPathBaseEmbeddings -from uipath_langchain_client.settings import UiPathAPIConfig +from uipath_langchain_client.settings import ( + ApiFlavor, + ApiType, + RoutingMode, + UiPathAPIConfig, + VendorType, +) class UiPathLiteLLMEmbeddings(UiPathBaseEmbeddings): diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index a1d545e..710e2d8 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -66,6 +66,7 @@ from uipath_langchain_client.base_client import UiPathBaseChatModel from uipath_langchain_client.settings import ApiType, RoutingMode, UiPathAPIConfig +from uipath_langchain_client.utils import is_anthropic_model_name _DictOrPydanticClass = Union[dict[str, Any], type[BaseModel], type] _DictOrPydantic = Union[dict[str, Any], BaseModel] @@ -412,7 +413,7 @@ def _preprocess_request( converted_message["content"] = "" if ( self.model_name - and "claude" in self.model_name.lower() + and is_anthropic_model_name(self.model_name) and not converted_message["content"] ): converted_message["content"] = "tool_call" diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py b/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py index d73b43e..de491ff 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py @@ -36,6 +36,7 @@ VendorType, get_default_client_settings, ) +from uipath_langchain_client.utils import is_anthropic_model_name def get_chat_model( @@ -172,7 +173,9 @@ def get_chat_model( **model_kwargs, ) case VendorType.AWSBEDROCK: - if model_family == ModelFamily.ANTHROPIC_CLAUDE and api_flavor is None: + if ( + model_family == ModelFamily.ANTHROPIC_CLAUDE and api_flavor != ApiFlavor.CONVERSE + ) or (api_flavor == ApiFlavor.INVOKE and is_anthropic_model_name(model_name)): from uipath_langchain_client.clients.bedrock.chat_models import ( UiPathChatAnthropicBedrock, ) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/utils.py b/packages/uipath_langchain_client/src/uipath_langchain_client/utils.py index b49e05a..0607b05 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/utils.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/utils.py @@ -13,6 +13,10 @@ UiPathTooManyRequestsError, UiPathUnprocessableEntityError, ) +from uipath.llm_client.utils.model_family import ( + ANTHROPIC_MODEL_NAME_KEYWORDS, + is_anthropic_model_name, +) from uipath.llm_client.utils.retry import RetryConfig __all__ = [ @@ -30,4 +34,6 @@ "UiPathServiceUnavailableError", "UiPathGatewayTimeoutError", "UiPathTooManyRequestsError", + "ANTHROPIC_MODEL_NAME_KEYWORDS", + "is_anthropic_model_name", ] diff --git a/src/uipath/llm_client/__version__.py b/src/uipath/llm_client/__version__.py index b7d80f3..e2f783d 100644 --- a/src/uipath/llm_client/__version__.py +++ b/src/uipath/llm_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LLM Client" __description__ = "A Python client for interacting with UiPath's LLM services." -__version__ = "1.9.0" +__version__ = "1.9.1" diff --git a/src/uipath/llm_client/clients/litellm/client.py b/src/uipath/llm_client/clients/litellm/client.py index 0f9ca01..cfa4f9a 100644 --- a/src/uipath/llm_client/clients/litellm/client.py +++ b/src/uipath/llm_client/clients/litellm/client.py @@ -35,6 +35,7 @@ RoutingMode, VendorType, ) +from uipath.llm_client.utils.model_family import is_anthropic_model_name from uipath.llm_client.utils.retry import RetryConfig # Route OpenAI chat completions through base_llm_http_handler (accepts HTTPHandler) @@ -191,7 +192,6 @@ def _discover_and_build_api_config( model_info = self._client_settings.get_model_info(self._model_name, vendor_type=vendor_type) model_family = model_info.get("modelFamily", None) - discovered_vendor = model_info.get("vendor", None) discovered_flavor = model_info.get("apiFlavor", None) @@ -217,20 +217,18 @@ def _discover_and_build_api_config( if resolved_flavor is None and resolved_vendor in ("openai", "azure"): resolved_flavor = ApiFlavor.CHAT_COMPLETIONS + # Claude detection: modelFamily from discovery, or name heuristic for BYOM + # (BYOM discovery does not expose modelFamily). + is_claude = model_family == ModelFamily.ANTHROPIC_CLAUDE or ( + model_family is None and is_anthropic_model_name(self._model_name) + ) + # Claude on Bedrock defaults to invoke - if ( - resolved_flavor is None - and resolved_vendor == "awsbedrock" - and model_family == ModelFamily.ANTHROPIC_CLAUDE - ): + if resolved_flavor is None and resolved_vendor == "awsbedrock" and is_claude: resolved_flavor = ApiFlavor.INVOKE # Claude on Vertex defaults to anthropic-claude - if ( - resolved_flavor is None - and resolved_vendor == "vertexai" - and model_family == ModelFamily.ANTHROPIC_CLAUDE - ): + if resolved_flavor is None and resolved_vendor == "vertexai" and is_claude: resolved_flavor = ApiFlavor.ANTHROPIC_CLAUDE api_config = UiPathAPIConfig( @@ -247,7 +245,9 @@ def _resolve_llm_provider(self) -> str: The model_family disambiguates cases where the same vendor hosts models from different providers (e.g. Claude on Vertex AI or Bedrock). """ - is_claude = self._model_family == ModelFamily.ANTHROPIC_CLAUDE + is_claude = self._model_family == ModelFamily.ANTHROPIC_CLAUDE or ( + self._model_family is None and is_anthropic_model_name(self._model_name) + ) vendor = str(self._api_config.vendor_type or "openai") # Claude on Vertex AI → vertex_ai (uses VertexAIAnthropicConfig) diff --git a/src/uipath/llm_client/utils/model_family.py b/src/uipath/llm_client/utils/model_family.py new file mode 100644 index 0000000..a7c7618 --- /dev/null +++ b/src/uipath/llm_client/utils/model_family.py @@ -0,0 +1,20 @@ +"""Heuristic helpers for identifying a model's family from its name. + +Discovery metadata (``modelFamily``) is the authoritative source, but BYOM +deployments do not expose it. These helpers provide a name-based fallback. +""" + +ANTHROPIC_MODEL_NAME_KEYWORDS: tuple[str, ...] = ( + "anthropic", + "claude", + "opus", + "sonnet", + "haiku", + "mythos", +) + + +def is_anthropic_model_name(model_name: str) -> bool: + """Return True if ``model_name`` looks like an Anthropic Claude-family model.""" + lower = model_name.lower() + return any(kw in lower for kw in ANTHROPIC_MODEL_NAME_KEYWORDS)