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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to `uipath_llm_client` (core package) will be documented in this file.

## [1.14.0] - 2026-06-15

### Added
- `ApiFlavor.ANTHROPIC_MESSAGES` (`"AnthropicMessages"`) for Bedrock-hosted Claude models that the discovery endpoint exposes with the native Anthropic Messages wire format. The value is the discovery string verbatim because it is forwarded as the `X-UiPath-LlmGateway-ApiFlavor` header. Also mapped in `API_FLAVOR_TO_VENDOR_TYPE` to `VendorType.AWSBEDROCK` so it resolves to the Bedrock passthrough route when discovery omits the vendor field.

## [1.13.1] - 2026-06-09

### Fixed
Expand Down
9 changes: 9 additions & 0 deletions packages/uipath_langchain_client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to `uipath_langchain_client` will be documented in this file.

## [1.14.0] - 2026-06-15

### Added
- Support for the `AnthropicMessages` API flavor on Bedrock-hosted Claude models. `get_chat_model` now routes discovery's `apiFlavor=AnthropicMessages` (vendor `AwsBedrock`) to `UiPathChatAnthropic` configured with `vendor_type=awsbedrock` and `api_flavor=ApiFlavor.ANTHROPIC_MESSAGES`. The client keeps the Bedrock passthrough URL but uses the native `Anthropic`/`AsyncAnthropic` SDK (model-in-body wire format), which the gateway requires for this flavor, instead of `AnthropicBedrock`.
- `UiPathChatAnthropic` now accepts an explicit `api_flavor`. When set to `ApiFlavor.ANTHROPIC_MESSAGES` it selects the native Anthropic SDK regardless of `vendor_type`; otherwise the flavor and SDK are derived from `vendor_type` exactly as before (`awsbedrock` → `invoke` + `AnthropicBedrock`, unchanged).

### Changed
- Bumped `uipath-llm-client` floor to `>=1.14.0` to pick up `ApiFlavor.ANTHROPIC_MESSAGES`.

## [1.13.1] - 2026-06-09

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath_langchain_client/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"langchain>=1.2.15,<2.0.0",
"uipath-llm-client>=1.13.1,<2.0.0",
"uipath-llm-client>=1.14.0,<2.0.0",
]

[project.optional-dependencies]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "UiPath LangChain Client"
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
__version__ = "1.13.1"
__version__ = "1.14.0"
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,20 @@ class UiPathChatAnthropic(UiPathBaseChatModel, ChatAnthropic):
freeze_base_url=True,
)
vendor_type: VendorType = VendorType.ANTHROPIC
api_flavor: ApiFlavor | str | None = None
"""Explicit API flavor. When ``ApiFlavor.ANTHROPIC_MESSAGES``, the request
uses the native Anthropic Messages wire format (model in body) while routing
through the hosting vendor's passthrough URL (e.g. ``vendor_type=awsbedrock``
for a Bedrock-hosted Claude). Otherwise the flavor is derived from
``vendor_type``."""

@model_validator(mode="after")
def setup_api_flavor_and_version(self) -> Self:
self.api_config.vendor_type = self.vendor_type
match self.api_flavor:
case ApiFlavor.ANTHROPIC_MESSAGES:
self.api_config.api_flavor = ApiFlavor.ANTHROPIC_MESSAGES
return self
match self.vendor_type:
case VendorType.ANTHROPIC:
self.api_config.api_flavor = None
Expand All @@ -67,6 +77,15 @@ def setup_api_flavor_and_version(self) -> Self:
def _anthropic_client(
self,
) -> Anthropic | AnthropicVertex | AnthropicBedrock | AnthropicFoundry:
match self.api_config.api_flavor:
case ApiFlavor.ANTHROPIC_MESSAGES:
return Anthropic(
api_key="PLACEHOLDER",
base_url=str(self.uipath_sync_client.base_url),
default_headers=dict(self.uipath_sync_client.headers),
max_retries=0, # handled by the UiPathBaseChatModel
http_client=self.uipath_sync_client,
)
match self.vendor_type:
case VendorType.ANTHROPIC:
return Anthropic(
Expand Down Expand Up @@ -111,6 +130,15 @@ def _anthropic_client(
def _async_anthropic_client(
self,
) -> AsyncAnthropic | AsyncAnthropicVertex | AsyncAnthropicBedrock | AsyncAnthropicFoundry:
match self.api_config.api_flavor:
case ApiFlavor.ANTHROPIC_MESSAGES:
return AsyncAnthropic(
api_key="PLACEHOLDER",
base_url=str(self.uipath_async_client.base_url),
default_headers=dict(self.uipath_async_client.headers),
max_retries=0, # handled by the UiPathBaseChatModel
http_client=self.uipath_async_client,
)
match self.vendor_type:
case VendorType.ANTHROPIC:
return AsyncAnthropic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ def get_chat_model(
**model_kwargs,
)
case VendorType.AWSBEDROCK:
if api_flavor == ApiFlavor.ANTHROPIC_MESSAGES:
from uipath_langchain_client.clients.anthropic.chat_models import (
UiPathChatAnthropic,
)

return UiPathChatAnthropic(
model=model_name,
settings=client_settings,
vendor_type=VendorType.AWSBEDROCK,
api_flavor=ApiFlavor.ANTHROPIC_MESSAGES,
byo_connection_id=byo_connection_id,
model_details=model_details,
**model_kwargs,
)

if api_flavor == ApiFlavor.INVOKE:
if model_family == ModelFamily.ANTHROPIC_CLAUDE:
from uipath_langchain_client.clients.bedrock.chat_models import (
Expand Down
2 changes: 1 addition & 1 deletion src/uipath/llm_client/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "UiPath LLM Client"
__description__ = "A Python client for interacting with UiPath's LLM services."
__version__ = "1.13.1"
__version__ = "1.14.0"
1 change: 1 addition & 0 deletions src/uipath/llm_client/clients/litellm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
ByomApiFlavor.GEMINI_EMBEDDINGS: "gemini",
ByomApiFlavor.AWS_BEDROCK_INVOKE: "bedrock",
ByomApiFlavor.AWS_BEDROCK_CONVERSE: "bedrock",
ByomApiFlavor.ANTHROPIC_MESSAGES: "bedrock",
Comment thread
ionut-mihalache-uipath marked this conversation as resolved.
}


Expand Down
4 changes: 4 additions & 0 deletions src/uipath/llm_client/settings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class ApiFlavor(StrEnum):
CONVERSE = "converse"
INVOKE = "invoke"
ANTHROPIC_CLAUDE = "anthropic-claude"
ANTHROPIC_MESSAGES = "AnthropicMessages"


class ByomApiFlavor(StrEnum):
Expand All @@ -44,6 +45,7 @@ class ByomApiFlavor(StrEnum):
GEMINI_EMBEDDINGS = "GeminiEmbeddings"
AWS_BEDROCK_INVOKE = "AwsBedrockInvoke"
AWS_BEDROCK_CONVERSE = "AwsBedrockConverse"
ANTHROPIC_MESSAGES = "AnthropicMessages"


API_FLAVOR_TO_VENDOR_TYPE: dict[str, VendorType] = {
Expand All @@ -53,6 +55,7 @@ class ByomApiFlavor(StrEnum):
ApiFlavor.ANTHROPIC_CLAUDE: VendorType.VERTEXAI,
ApiFlavor.CONVERSE: VendorType.AWSBEDROCK,
ApiFlavor.INVOKE: VendorType.AWSBEDROCK,
ByomApiFlavor.ANTHROPIC_MESSAGES: VendorType.AWSBEDROCK,
ByomApiFlavor.OPENAI_CHAT_COMPLETIONS: VendorType.OPENAI,
ByomApiFlavor.OPENAI_RESPONSES: VendorType.OPENAI,
ByomApiFlavor.OPENAI_EMBEDDINGS: VendorType.OPENAI,
Expand All @@ -69,4 +72,5 @@ class ByomApiFlavor(StrEnum):
ByomApiFlavor.GEMINI_GENERATE_CONTENT: ApiFlavor.GENERATE_CONTENT,
ByomApiFlavor.AWS_BEDROCK_INVOKE: ApiFlavor.INVOKE,
ByomApiFlavor.AWS_BEDROCK_CONVERSE: ApiFlavor.CONVERSE,
ByomApiFlavor.ANTHROPIC_MESSAGES: ApiFlavor.ANTHROPIC_MESSAGES,
}
12 changes: 11 additions & 1 deletion tests/core/features/test_api_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
import pytest

from uipath.llm_client.settings import UiPathAPIConfig
from uipath.llm_client.settings.constants import ApiFlavor, ApiType, RoutingMode, VendorType
from uipath.llm_client.settings.constants import (
API_FLAVOR_TO_VENDOR_TYPE,
ApiFlavor,
ApiType,
RoutingMode,
VendorType,
)


class TestUiPathAPIConfig:
Expand Down Expand Up @@ -83,6 +89,10 @@ def test_api_flavor_values(self):
assert ApiFlavor.CONVERSE == "converse"
assert ApiFlavor.INVOKE == "invoke"
assert ApiFlavor.ANTHROPIC_CLAUDE == "anthropic-claude"
assert ApiFlavor.ANTHROPIC_MESSAGES == "AnthropicMessages"

def test_anthropic_messages_maps_to_bedrock_vendor(self):
assert API_FLAVOR_TO_VENDOR_TYPE[ApiFlavor.ANTHROPIC_MESSAGES] == VendorType.AWSBEDROCK

def test_enum_string_comparison(self):
assert ApiType.COMPLETIONS == "completions"
Expand Down
72 changes: 71 additions & 1 deletion tests/langchain/clients/anthropic/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
from typing import Any

import pytest
from anthropic import (
Anthropic,
AnthropicBedrock,
AsyncAnthropic,
AsyncAnthropicBedrock,
)
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_tests.unit_tests import ChatModelUnitTests
from uipath_langchain_client.clients.anthropic.chat_models import UiPathChatAnthropic

from uipath.llm_client.settings import UiPathBaseSettings
from uipath.llm_client.settings import ApiFlavor, UiPathBaseSettings, VendorType

ANTHROPIC_CHAT_CLASSES = [UiPathChatAnthropic]

Expand All @@ -32,3 +38,67 @@ def chat_model_params(self) -> dict[str, Any]:

@pytest.mark.xfail(reason="Skipping serdes test for now")
def test_serdes(self, *args: Any, **kwargs: Any) -> None: ...


def _build(client_settings: UiPathBaseSettings, **kwargs: Any) -> UiPathChatAnthropic:
return UiPathChatAnthropic(
model="anthropic.claude-sonnet-4-6",
settings=client_settings,
model_details={},
**kwargs,
)


class TestAnthropicMessagesFlavor:
"""AnthropicMessages uses the native Anthropic SDK (model-in-body wire format)
over the awsbedrock passthrough URL."""

def test_sets_anthropic_messages_flavor_over_bedrock_url(
self, client_settings: UiPathBaseSettings
):
chat = _build(
client_settings,
vendor_type=VendorType.AWSBEDROCK,
api_flavor=ApiFlavor.ANTHROPIC_MESSAGES,
)
assert chat.api_config.vendor_type == VendorType.AWSBEDROCK
assert chat.api_config.api_flavor == ApiFlavor.ANTHROPIC_MESSAGES

def test_uses_native_anthropic_sdk(self, client_settings: UiPathBaseSettings):
chat = _build(
client_settings,
vendor_type=VendorType.AWSBEDROCK,
api_flavor=ApiFlavor.ANTHROPIC_MESSAGES,
)
assert isinstance(chat._anthropic_client, Anthropic)
assert not isinstance(chat._anthropic_client, AnthropicBedrock)
assert isinstance(chat._async_anthropic_client, AsyncAnthropic)
assert not isinstance(chat._async_anthropic_client, AsyncAnthropicBedrock)

def test_flavor_is_orthogonal_to_vendor_type(self, client_settings: UiPathBaseSettings):
chat = _build(
client_settings,
vendor_type=VendorType.ANTHROPIC,
api_flavor=ApiFlavor.ANTHROPIC_MESSAGES,
)
assert chat.api_config.api_flavor == ApiFlavor.ANTHROPIC_MESSAGES
assert isinstance(chat._anthropic_client, Anthropic)


class TestVendorDerivedDefaultsUnchanged:
"""Regression guard: omitting api_flavor preserves the prior vendor-derived behavior."""

def test_bedrock_without_flavor_uses_invoke_and_anthropic_bedrock(
self, client_settings: UiPathBaseSettings
):
chat = _build(client_settings, vendor_type=VendorType.AWSBEDROCK)
assert chat.api_config.api_flavor == ApiFlavor.INVOKE
assert isinstance(chat._anthropic_client, AnthropicBedrock)
assert isinstance(chat._async_anthropic_client, AsyncAnthropicBedrock)

def test_anthropic_vendor_defaults_to_anthropic_client(
self, client_settings: UiPathBaseSettings
):
chat = _build(client_settings, vendor_type=VendorType.ANTHROPIC)
assert isinstance(chat._anthropic_client, Anthropic)
assert not isinstance(chat._anthropic_client, AnthropicBedrock)
48 changes: 47 additions & 1 deletion tests/langchain/features/test_factory_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from uipath_langchain_client.factory import get_chat_model, get_embedding_model

from tests.langchain.conftest import COMPLETION_MODEL_NAMES, EMBEDDING_MODEL_NAMES
from uipath.llm_client.settings import ApiFlavor, UiPathBaseSettings
from uipath.llm_client.settings import ApiFlavor, UiPathBaseSettings, VendorType


@pytest.mark.vcr
Expand Down Expand Up @@ -333,3 +333,49 @@ def test_no_kwarg_keeps_settings_value(self, monkeypatch: pytest.MonkeyPatch):
)
assert captured["settings"] is original
original.model_copy.assert_not_called()


class TestFactoryAnthropicMessagesRouting:
"""AwsBedrock + ``apiFlavor=AnthropicMessages`` routes to ``UiPathChatAnthropic``
configured for the native Anthropic Messages wire format over the Bedrock
passthrough URL (not the Bedrock Converse/Invoke clients)."""

def _capture(
self,
monkeypatch: pytest.MonkeyPatch,
model_info: dict,
**factory_kwargs,
) -> dict:
settings = MagicMock()
settings.get_model_info.return_value = model_info
captured: dict = {}

class _StubModel:
def __init__(self, **kwargs):
captured.update(kwargs)

monkeypatch.setattr(
"uipath_langchain_client.clients.anthropic.chat_models.UiPathChatAnthropic",
_StubModel,
)
get_chat_model(
model_name=model_info["modelName"],
client_settings=settings,
**factory_kwargs,
)
return captured

def test_anthropic_messages_routes_to_uipath_chat_anthropic(
self, monkeypatch: pytest.MonkeyPatch
):
captured = self._capture(
monkeypatch,
{
"modelName": "anthropic.claude-sonnet-4-6",
"vendor": "AwsBedrock",
"apiFlavor": "AnthropicMessages",
"modelFamily": "AnthropicClaude",
},
)
assert captured["vendor_type"] == VendorType.AWSBEDROCK
assert captured["api_flavor"] == ApiFlavor.ANTHROPIC_MESSAGES
Loading