Skip to content

Commit ea29e15

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 ea29e15

8 files changed

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

735
__all__ = [
836
"UiPathChatBedrock",

src/uipath_langchain/chat/models.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
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-
)
1+
from .openai import UiPathAzureChatOpenAI, UiPathChat, UiPathChatOpenAI
62

73
__all__ = [
84
"UiPathChat",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1+
import os
2+
3+
from uipath_langchain_client.clients.normalized.chat_models import UiPathChat
14
from uipath_langchain_client.clients.openai.chat_models import (
25
UiPathAzureChatOpenAI,
36
UiPathChatOpenAI,
47
)
58

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

src/uipath_langchain/chat/vertex.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@
22
UiPathChatGoogleGenerativeAI,
33
)
44

5+
DEFAULT_MODEL_NAME = "gemini-2.5-flash"
6+
7+
8+
def _default_factory() -> str:
9+
return DEFAULT_MODEL_NAME
10+
11+
12+
UiPathChatGoogleGenerativeAI.model_fields[
13+
"model_name"
14+
].default_factory = _default_factory
15+
UiPathChatGoogleGenerativeAI.model_rebuild(force=True)
16+
517
UiPathChatVertex = UiPathChatGoogleGenerativeAI
618

19+
720
__all__ = [
821
"UiPathChatGoogleGenerativeAI",
922
"UiPathChatVertex",
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 TestOpenAIEnvVarOverride:
88+
def test_env_var_overrides_openai_default(self, monkeypatch):
89+
monkeypatch.setenv("UIPATH_MODEL_NAME", "gpt-5-preview")
90+
llm = UiPathChat()
91+
assert llm.model_name == "gpt-5-preview"
92+
93+
def test_env_var_does_not_leak_into_bedrock(self, monkeypatch):
94+
monkeypatch.setenv("UIPATH_MODEL_NAME", "gpt-5-preview")
95+
llm = UiPathChatBedrock()
96+
assert llm.model_name == _DEFAULT_BEDROCK
97+
98+
def test_env_var_does_not_leak_into_vertex(self, monkeypatch):
99+
monkeypatch.setenv("UIPATH_MODEL_NAME", "gpt-5-preview")
100+
llm = UiPathChatGoogleGenerativeAI()
101+
assert llm.model_name == _DEFAULT_VERTEX
102+
103+
104+
class TestExportsWithoutDefaults:
105+
def test_anthropic_raises_without_model(self):
106+
with pytest.raises(ValidationError, match="model"):
107+
UiPathChatAnthropic()
108+
109+
def test_anthropic_vertex_raises_without_model(self):
110+
with pytest.raises(ValidationError, match="model"):
111+
UiPathChatAnthropicVertex()
112+
113+
def test_fireworks_raises_without_model(self):
114+
with pytest.raises(ValidationError, match="model"):
115+
UiPathChatFireworks()
116+
117+
118+
class TestReExportedClassIdentity:
119+
def test_uipath_chat_is_upstream_class(self):
120+
assert UiPathChat is _UpstreamUiPathChat
121+
122+
def test_uipath_chat_vertex_alias_matches_google(self):
123+
assert UiPathChatVertex is UiPathChatGoogleGenerativeAI
124+
125+
def test_bedrock_converse_is_subclass_of_upstream(self):
126+
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)