Skip to content

Commit 87a173a

Browse files
cosminachoclaude
andcommitted
feat: per-vendor model_name defaults on new chat clients
Ports the legacy model_name defaults from the pre-migration UiPathRequestMixin / BedrockModels / GeminiModels over to the new uipath_langchain_client-backed re-exports so callers can construct UiPathChat(), UiPathChatBedrock(), UiPathChatVertex() etc. with no args. Defaults per vendor: - OpenAI / Azure (UiPathChat, UiPathAzureChatOpenAI, UiPathChatOpenAI): UIPATH_MODEL_NAME env var, fallback gpt-4.1-mini-2025-04-14. - Bedrock (UiPathChatBedrock, UiPathChatAnthropicBedrock, UiPathChatBedrockConverse): anthropic.claude-haiku-4-5-20251001-v1:0. - Vertex (UiPathChatGoogleGenerativeAI, UiPathChatVertex): gemini-2.5-flash. Classes without a legacy equivalent (UiPathChatAnthropic, UiPathChatAnthropicVertex, UiPathChatFireworks) keep model= required. Implementation: defaults are attached inline in the per-vendor re-export files (chat/openai.py, chat/bedrock.py, chat/vertex.py, chat/models.py). For most classes this is a one-liner FieldInfo mutation (cls.model_fields["model_name"].default_factory = factory; cls.model_rebuild(force=True)) which pydantic V2 scopes per class. UiPathChatBedrockConverse is the one class where the field-default path does not suffice — its upstream ChatBedrockConverse.set_disable_streaming before-validator reads model / model_id from the raw input dict before field defaults fire — so it gets a thin subclass with our own @model_validator(mode="before") that injects the default into values. Version bump 0.10.5 -> 0.10.6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bbf2e25 commit 87a173a

8 files changed

Lines changed: 216 additions & 36 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.10.5"
3+
version = "0.10.6"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/chat/__init__.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,35 @@ def __getattr__(name):
2424

2525
return get_chat_model
2626
if name == "UiPathChat":
27-
from uipath_langchain_client.clients.normalized.chat_models import (
28-
UiPathChat,
29-
)
27+
from .models import UiPathChat
3028

3129
return UiPathChat
3230
if name == "UiPathAzureChatOpenAI":
33-
from uipath_langchain_client.clients.openai.chat_models import (
34-
UiPathAzureChatOpenAI,
35-
)
31+
from .openai import UiPathAzureChatOpenAI
3632

3733
return UiPathAzureChatOpenAI
3834
if name == "UiPathChatOpenAI":
39-
from uipath_langchain_client.clients.openai.chat_models import (
40-
UiPathChatOpenAI,
41-
)
35+
from .openai import UiPathChatOpenAI
4236

4337
return UiPathChatOpenAI
4438
if name == "UiPathChatGoogleGenerativeAI":
45-
from uipath_langchain_client.clients.google.chat_models import (
46-
UiPathChatGoogleGenerativeAI,
47-
)
39+
from .vertex import UiPathChatGoogleGenerativeAI
4840

4941
return UiPathChatGoogleGenerativeAI
42+
if name == "UiPathChatVertex":
43+
from .vertex import UiPathChatVertex
44+
45+
return UiPathChatVertex
5046
if name == "UiPathChatBedrock":
51-
from uipath_langchain_client.clients.bedrock.chat_models import (
52-
UiPathChatBedrock,
53-
)
47+
from .bedrock import UiPathChatBedrock
5448

5549
return UiPathChatBedrock
5650
if name == "UiPathChatBedrockConverse":
57-
from uipath_langchain_client.clients.bedrock.chat_models import (
58-
UiPathChatBedrockConverse,
59-
)
51+
from .bedrock import UiPathChatBedrockConverse
6052

6153
return UiPathChatBedrockConverse
6254
if name == "UiPathChatAnthropicBedrock":
63-
from uipath_langchain_client.clients.bedrock.chat_models import (
64-
UiPathChatAnthropicBedrock,
65-
)
55+
from .bedrock import UiPathChatAnthropicBedrock
6656

6757
return UiPathChatAnthropicBedrock
6858
if name == "UiPathChatAnthropic":
@@ -83,12 +73,6 @@ def __getattr__(name):
8373
)
8474

8575
return UiPathChatFireworks
86-
if name == "UiPathChatVertex":
87-
from uipath_langchain_client.clients.google.chat_models import (
88-
UiPathChatGoogleGenerativeAI,
89-
)
90-
91-
return UiPathChatGoogleGenerativeAI
9276
if name in ("OpenAIModels", "BedrockModels", "GeminiModels"):
9377
from uipath_langchain.chat._legacy import supported_models
9478

src/uipath_langchain/chat/bedrock.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
1+
import os
2+
from typing import Any
3+
4+
from pydantic import model_validator
15
from uipath_langchain_client.clients.bedrock.chat_models import (
26
UiPathChatAnthropicBedrock,
37
UiPathChatBedrock,
4-
UiPathChatBedrockConverse,
58
)
9+
from uipath_langchain_client.clients.bedrock.chat_models import (
10+
UiPathChatBedrockConverse as _UpstreamUiPathChatBedrockConverse,
11+
)
12+
13+
DEFAULT_MODEL_NAME = "anthropic.claude-haiku-4-5-20251001-v1:0"
14+
15+
16+
def _default_factory() -> str:
17+
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)
18+
19+
20+
for _cls in (UiPathChatBedrock, UiPathChatAnthropicBedrock):
21+
_cls.model_fields["model_name"].default_factory = _default_factory
22+
_cls.model_rebuild(force=True)
23+
24+
25+
class UiPathChatBedrockConverse(_UpstreamUiPathChatBedrockConverse):
26+
@model_validator(mode="before")
27+
@classmethod
28+
def _inject_default_model(cls, values: Any) -> Any:
29+
if isinstance(values, dict) and not any(
30+
k in values for k in ("model", "model_id", "model_name")
31+
):
32+
values = {**values, "model": _default_factory()}
33+
return values
34+
635

736
__all__ = [
837
"UiPathChatBedrock",
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import os
2+
13
from uipath_langchain_client.clients.normalized.chat_models import UiPathChat
2-
from uipath_langchain_client.clients.openai.chat_models import (
3-
UiPathAzureChatOpenAI,
4-
UiPathChatOpenAI,
5-
)
4+
5+
DEFAULT_MODEL_NAME = "gpt-4.1-mini-2025-04-14"
6+
7+
8+
def _default_factory() -> str:
9+
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)
10+
11+
12+
UiPathChat.model_fields["model_name"].default_factory = _default_factory
13+
UiPathChat.model_rebuild(force=True)
14+
615

716
__all__ = [
817
"UiPathChat",
9-
"UiPathAzureChatOpenAI",
10-
"UiPathChatOpenAI",
1118
]

src/uipath_langchain/chat/openai.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import os
2+
13
from uipath_langchain_client.clients.openai.chat_models import (
24
UiPathAzureChatOpenAI,
35
UiPathChatOpenAI,
46
)
57

8+
DEFAULT_MODEL_NAME = "gpt-4.1-mini-2025-04-14"
9+
10+
11+
def _default_factory() -> str:
12+
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)
13+
14+
15+
for _cls in (UiPathAzureChatOpenAI, UiPathChatOpenAI):
16+
_cls.model_fields["model_name"].default_factory = _default_factory
17+
_cls.model_rebuild(force=True)
18+
19+
620
__all__ = [
721
"UiPathAzureChatOpenAI",
822
"UiPathChatOpenAI",

src/uipath_langchain/chat/vertex.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1+
import os
2+
13
from uipath_langchain_client.clients.google.chat_models import (
24
UiPathChatGoogleGenerativeAI,
35
)
46

7+
DEFAULT_MODEL_NAME = "gemini-2.5-flash"
8+
9+
10+
def _default_factory() -> str:
11+
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)
12+
13+
14+
UiPathChatGoogleGenerativeAI.model_fields[
15+
"model_name"
16+
].default_factory = _default_factory
17+
UiPathChatGoogleGenerativeAI.model_rebuild(force=True)
18+
519
UiPathChatVertex = UiPathChatGoogleGenerativeAI
620

21+
722
__all__ = [
823
"UiPathChatGoogleGenerativeAI",
924
"UiPathChatVertex",
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import pytest
2+
from pydantic import ValidationError
3+
from uipath_langchain_client.clients.bedrock.chat_models import (
4+
UiPathChatBedrockConverse as _UpstreamBedrockConverse,
5+
)
6+
from uipath_langchain_client.clients.normalized.chat_models import (
7+
UiPathChat as _UpstreamUiPathChat,
8+
)
9+
10+
from uipath_langchain.chat import (
11+
UiPathAzureChatOpenAI,
12+
UiPathChat,
13+
UiPathChatAnthropic,
14+
UiPathChatAnthropicBedrock,
15+
UiPathChatAnthropicVertex,
16+
UiPathChatBedrock,
17+
UiPathChatBedrockConverse,
18+
UiPathChatFireworks,
19+
UiPathChatGoogleGenerativeAI,
20+
UiPathChatOpenAI,
21+
UiPathChatVertex,
22+
)
23+
24+
_DEFAULT_OPENAI = "gpt-4.1-mini-2025-04-14"
25+
_DEFAULT_BEDROCK = "anthropic.claude-haiku-4-5-20251001-v1:0"
26+
_DEFAULT_VERTEX = "gemini-2.5-flash"
27+
28+
_FAKE_JWT = (
29+
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9."
30+
"eyJzdWIiOiAidGVzdCIsICJpc3MiOiAidGVzdCJ9."
31+
"signature"
32+
)
33+
34+
35+
@pytest.fixture(autouse=True)
36+
def _platform_env(monkeypatch):
37+
monkeypatch.setenv("UIPATH_ACCESS_TOKEN", _FAKE_JWT)
38+
monkeypatch.setenv("UIPATH_URL", "https://example.com/org/tenant/orchestrator_/")
39+
monkeypatch.setenv("UIPATH_TENANT_ID", "tenant")
40+
monkeypatch.setenv("UIPATH_ORGANIZATION_ID", "org")
41+
monkeypatch.delenv("UIPATH_MODEL_NAME", raising=False)
42+
43+
44+
_CASES = [
45+
(UiPathChat, _DEFAULT_OPENAI),
46+
(UiPathAzureChatOpenAI, _DEFAULT_OPENAI),
47+
(UiPathChatOpenAI, _DEFAULT_OPENAI),
48+
(UiPathChatGoogleGenerativeAI, _DEFAULT_VERTEX),
49+
(UiPathChatVertex, _DEFAULT_VERTEX),
50+
(UiPathChatBedrock, _DEFAULT_BEDROCK),
51+
(UiPathChatBedrockConverse, _DEFAULT_BEDROCK),
52+
(UiPathChatAnthropicBedrock, _DEFAULT_BEDROCK),
53+
]
54+
55+
56+
@pytest.mark.parametrize("cls, expected", _CASES)
57+
class TestInstantiationWithoutModelKwarg:
58+
def test_no_args_uses_default(self, cls, expected):
59+
llm = cls()
60+
assert llm.model_name == expected
61+
62+
def test_explicit_model_kwarg_overrides_default(self, cls, expected):
63+
llm = cls(model="custom-model-id")
64+
assert llm.model_name == "custom-model-id"
65+
66+
67+
def test_uipath_chat_no_args():
68+
llm = UiPathChat()
69+
assert llm.model_name == _DEFAULT_OPENAI
70+
71+
72+
def test_uipath_chat_bedrock_no_args():
73+
llm = UiPathChatBedrock()
74+
assert llm.model_name == _DEFAULT_BEDROCK
75+
76+
77+
def test_uipath_chat_bedrock_converse_no_args():
78+
llm = UiPathChatBedrockConverse()
79+
assert llm.model_name == _DEFAULT_BEDROCK
80+
81+
82+
def test_uipath_chat_vertex_no_args():
83+
llm = UiPathChatVertex()
84+
assert llm.model_name == _DEFAULT_VERTEX
85+
86+
87+
class TestUipathModelNameEnvVarOverride:
88+
def test_env_var_overrides_openai_default(self, monkeypatch):
89+
monkeypatch.setenv("UIPATH_MODEL_NAME", "custom-override")
90+
llm = UiPathChat()
91+
assert llm.model_name == "custom-override"
92+
93+
def test_env_var_overrides_bedrock_default(self, monkeypatch):
94+
monkeypatch.setenv("UIPATH_MODEL_NAME", "custom-override")
95+
llm = UiPathChatBedrock()
96+
assert llm.model_name == "custom-override"
97+
98+
def test_env_var_overrides_bedrock_converse_default(self, monkeypatch):
99+
monkeypatch.setenv("UIPATH_MODEL_NAME", "custom-override")
100+
llm = UiPathChatBedrockConverse()
101+
assert llm.model_name == "custom-override"
102+
103+
def test_env_var_overrides_vertex_default(self, monkeypatch):
104+
monkeypatch.setenv("UIPATH_MODEL_NAME", "custom-override")
105+
llm = UiPathChatGoogleGenerativeAI()
106+
assert llm.model_name == "custom-override"
107+
108+
109+
class TestExportsWithoutDefaults:
110+
def test_anthropic_raises_without_model(self):
111+
with pytest.raises(ValidationError, match="model"):
112+
UiPathChatAnthropic()
113+
114+
def test_anthropic_vertex_raises_without_model(self):
115+
with pytest.raises(ValidationError, match="model"):
116+
UiPathChatAnthropicVertex()
117+
118+
def test_fireworks_raises_without_model(self):
119+
with pytest.raises(ValidationError, match="model"):
120+
UiPathChatFireworks()
121+
122+
123+
class TestReExportedClassIdentity:
124+
def test_uipath_chat_is_upstream_class(self):
125+
assert UiPathChat is _UpstreamUiPathChat
126+
127+
def test_uipath_chat_vertex_alias_matches_google(self):
128+
assert UiPathChatVertex is UiPathChatGoogleGenerativeAI
129+
130+
def test_bedrock_converse_is_subclass_of_upstream(self):
131+
assert issubclass(UiPathChatBedrockConverse, _UpstreamBedrockConverse)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)