Skip to content

Commit a9cffaf

Browse files
cosminachoclaude
andauthored
Feat: strip sampling params when modelDetails.shouldSkipTemperature is true (#74)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 24a9e11 commit a9cffaf

14 files changed

Lines changed: 766 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

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

5+
## [1.10.0] - 2026-04-23
6+
7+
### Added
8+
- `uipath.llm_client.utils.sampling` module exposing `DISABLED_SAMPLING_PARAMS`, `disabled_params_from_model_details`, `is_disabled_value`, and `strip_disabled_kwargs`. The helpers use the langchain-openai-style `disabled_params` format (`{name: None | [values]}`) so they compose with the existing `langchain_openai._filter_disabled_params` path. `disabled_params_from_model_details` derives the disabled-param map from a discovery-endpoint `modelDetails` dict (today: `shouldSkipTemperature=True` disables the full sampling set — temperature, top_p, top_k, frequency/presence penalty, seed, logit_bias, logprobs, top_logprobs).
9+
510
## [1.9.9] - 2026-04-23
611

712
### Changed

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

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

5+
## [1.10.0] - 2026-04-23
6+
7+
### Added
8+
- `model_details` and `disabled_params` fields on `UiPathBaseLLMClient`, plus a single `@model_validator(mode="after") setup_model_info` that (1) forwards the factory-supplied `model_details` or fetches it from `client_settings.get_model_info`, and (2) sets `disabled_params` to the merge of what the caller passed and what `disabled_params_from_model_details` derives — user keys win on conflicts, so callers can override any derived entry by name.
9+
- `disabled_params` uses the langchain-openai shape (`{name: None | [values]}`), so subclasses inheriting from `ChatOpenAI` / `AzureChatOpenAI` also benefit from the native `_filter_disabled_params` path inside `bind_tools`.
10+
- Runtime stripping in the four `_generate`/`_agenerate`/`_stream`/`_astream` wrappers on `UiPathBaseChatModel` delegates to `uipath.llm_client.utils.sampling.strip_disabled_kwargs`, generic over `disabled_params`. A warning is logged via `self.logger` for each stripped key when a logger is configured. Fixes `anthropic.claude-opus-4-7` rejecting any sampling parameter passed via `.invoke()` / `.ainvoke()` / streams.
11+
12+
### Removed
13+
- The unused `disabled_params` field declaration on `UiPathChat` (now inherited from `UiPathBaseLLMClient`).
14+
15+
### Changed
16+
- Bumped `uipath-llm-client` floor to `>=1.10.0` to match the release that adds `uipath.llm_client.utils.sampling`.
17+
18+
### Known follow-up
19+
- Init-time values set on the instance (`UiPathChat(model="anthropic.claude-opus-4-7", temperature=0.5)`) still flow into the outgoing request body via `_default_params` / the vendor SDK. The runtime invoke-time strip handles `.invoke(..., temperature=...)`; a follow-up will plug the init-time leak using the already-populated `disabled_params`.
20+
521
## [1.9.9] - 2026-04-23
622

723
### Changed

packages/uipath_langchain_client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"langchain>=1.2.15,<2.0.0",
9-
"uipath-llm-client>=1.9.9,<2.0.0",
9+
"uipath-llm-client>=1.10.0,<2.0.0",
1010
]
1111

1212
[project.optional-dependencies]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LangChain Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
3-
__version__ = "1.9.9"
3+
__version__ = "1.10.0"

packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from abc import ABC
2828
from collections.abc import AsyncGenerator, Generator, Mapping, Sequence
2929
from functools import cached_property
30-
from typing import Any, ClassVar, Literal
30+
from typing import Any, ClassVar, Literal, Self
3131

3232
from httpx import URL, Response
3333
from langchain_core.callbacks import (
@@ -38,7 +38,7 @@
3838
from langchain_core.language_models.chat_models import BaseChatModel
3939
from langchain_core.messages import BaseMessage
4040
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
41-
from pydantic import AliasChoices, BaseModel, ConfigDict, Field
41+
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, model_validator
4242

4343
from uipath.llm_client.httpx_client import (
4444
UiPathHttpxAsyncClient,
@@ -49,6 +49,10 @@
4949
get_captured_response_headers,
5050
set_captured_response_headers,
5151
)
52+
from uipath.llm_client.utils.sampling import (
53+
disabled_params_from_model_details,
54+
strip_disabled_kwargs,
55+
)
5256
from uipath_langchain_client.settings import (
5357
UiPathAPIConfig,
5458
UiPathBaseSettings,
@@ -108,6 +112,19 @@ class UiPathBaseLLMClient(BaseModel, ABC):
108112
description="Settings for the UiPath client (defaults based on UIPATH_LLM_SERVICE env var)",
109113
)
110114

115+
model_details: dict[str, Any] | None = Field(
116+
default=None,
117+
description="Per-model capability flags sourced from the discovery endpoint "
118+
"(e.g. {'shouldSkipTemperature': True}). Passed through by the factory; "
119+
"resolved from client_settings.get_model_info otherwise.",
120+
)
121+
disabled_params: dict[str, Any] | None = Field(
122+
default=None,
123+
description="langchain-openai-style map of parameters that must not be sent to "
124+
"this model. Keys are param names; values are None (always disabled) or a list "
125+
"of disallowed values. Derived from ``model_details`` when not provided.",
126+
)
127+
111128
default_headers: Mapping[str, str] | None = Field(
112129
default=None,
113130
description="Caller-supplied request headers. Merged on top of `class_default_headers`; "
@@ -140,6 +157,39 @@ class UiPathBaseLLMClient(BaseModel, ABC):
140157
description="Logger for request/response logging",
141158
)
142159

160+
@model_validator(mode="after")
161+
def setup_model_info(self) -> Self:
162+
"""Resolve ``model_details`` from discovery and merge ``disabled_params``.
163+
164+
Runs after pydantic has validated the fields, so ``self.client_settings``
165+
(with its ``default_factory``) and ``self.model_name`` are already live.
166+
167+
``model_details`` is resolved once: caller-forwarded value wins, then a
168+
lookup against ``client_settings.get_model_info`` (backed by the
169+
class-cached discovery response), else an empty mapping on failure.
170+
171+
``disabled_params`` is the merge of what the caller passed and what we
172+
can derive from ``model_details`` (via
173+
``disabled_params_from_model_details``). User-provided keys win on
174+
conflicts, so callers can override a derived entry by name.
175+
"""
176+
if self.model_details is None:
177+
try:
178+
info = self.client_settings.get_model_info(
179+
self.model_name,
180+
byo_connection_id=self.byo_connection_id,
181+
)
182+
self.model_details = info.get("modelDetails") or {}
183+
except Exception:
184+
self.model_details = {}
185+
186+
derived = disabled_params_from_model_details(self.model_details) or {}
187+
user_provided = self.disabled_params or {}
188+
merged = {**derived, **user_provided}
189+
self.disabled_params = merged or None
190+
191+
return self
192+
143193
@cached_property
144194
def uipath_sync_client(self) -> UiPathHttpxClient:
145195
"""Here we instantiate a synchronous HTTP client with the proper authentication pipeline, retry logic, logging etc."""
@@ -364,6 +414,12 @@ def _generate(
364414
run_manager: CallbackManagerForLLMRun | None = None,
365415
**kwargs: Any,
366416
) -> ChatResult:
417+
kwargs = strip_disabled_kwargs(
418+
kwargs,
419+
disabled_params=self.disabled_params,
420+
model_name=self.model_name,
421+
logger=self.logger,
422+
)
367423
set_captured_response_headers({})
368424
try:
369425
result = self._uipath_generate(messages, stop=stop, run_manager=run_manager, **kwargs)
@@ -389,6 +445,12 @@ async def _agenerate(
389445
run_manager: AsyncCallbackManagerForLLMRun | None = None,
390446
**kwargs: Any,
391447
) -> ChatResult:
448+
kwargs = strip_disabled_kwargs(
449+
kwargs,
450+
disabled_params=self.disabled_params,
451+
model_name=self.model_name,
452+
logger=self.logger,
453+
)
392454
set_captured_response_headers({})
393455
try:
394456
result = await self._uipath_agenerate(
@@ -416,6 +478,12 @@ def _stream(
416478
run_manager: CallbackManagerForLLMRun | None = None,
417479
**kwargs: Any,
418480
) -> Generator[ChatGenerationChunk, None, None]:
481+
kwargs = strip_disabled_kwargs(
482+
kwargs,
483+
disabled_params=self.disabled_params,
484+
model_name=self.model_name,
485+
logger=self.logger,
486+
)
419487
set_captured_response_headers({})
420488
try:
421489
first = True
@@ -446,6 +514,12 @@ async def _astream(
446514
run_manager: AsyncCallbackManagerForLLMRun | None = None,
447515
**kwargs: Any,
448516
) -> AsyncGenerator[ChatGenerationChunk, None]:
517+
kwargs = strip_disabled_kwargs(
518+
kwargs,
519+
disabled_params=self.disabled_params,
520+
model_name=self.model_name,
521+
logger=self.logger,
522+
)
449523
set_captured_response_headers({})
450524
try:
451525
first = True

packages/uipath_langchain_client/src/uipath_langchain_client/clients/litellm/embeddings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
>>> vectors = embeddings.embed_documents(["Hello world"])
1111
"""
1212

13-
from __future__ import annotations
14-
1513
from pydantic import Field, model_validator
1614
from typing_extensions import Self
1715

packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464
from langchain_core.utils.pydantic import is_basemodel_subclass
6565
from pydantic import AliasChoices, BaseModel, Field
6666

67+
from uipath.llm_client.utils.model_family import is_anthropic_model_name
6768
from uipath_langchain_client.base_client import UiPathBaseChatModel
6869
from uipath_langchain_client.settings import ApiType, RoutingMode, UiPathAPIConfig
69-
from uipath_langchain_client.utils import is_anthropic_model_name
7070

7171
_DictOrPydanticClass = Union[dict[str, Any], type[BaseModel], type]
7272
_DictOrPydantic = Union[dict[str, Any], BaseModel]
@@ -179,7 +179,6 @@ class UiPathChat(UiPathBaseChatModel):
179179
seed: int | None = None
180180

181181
model_kwargs: dict[str, Any] = Field(default_factory=dict)
182-
disabled_params: dict[str, Any] | None = None
183182

184183
# OpenAI
185184
logit_bias: dict[str, int] | None = None

packages/uipath_langchain_client/src/uipath_langchain_client/factory.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from typing import Any
2424

25+
from uipath.llm_client.utils.model_family import is_anthropic_model_name
2526
from uipath_langchain_client.base_client import (
2627
UiPathBaseChatModel,
2728
UiPathBaseEmbeddings,
@@ -36,7 +37,6 @@
3637
VendorType,
3738
get_default_client_settings,
3839
)
39-
from uipath_langchain_client.utils import is_anthropic_model_name
4040

4141

4242
def get_chat_model(
@@ -85,12 +85,14 @@ def get_chat_model(
8585
vendor_type=vendor_type,
8686
)
8787
model_family = model_info.get("modelFamily", None)
88+
model_details = model_info.get("modelDetails") or {}
8889

8990
if custom_class is not None:
9091
return custom_class(
9192
model=model_name,
9293
settings=client_settings,
9394
byo_connection_id=byo_connection_id,
95+
model_details=model_details,
9496
**model_kwargs,
9597
)
9698

@@ -103,6 +105,7 @@ def get_chat_model(
103105
model=model_name,
104106
settings=client_settings,
105107
byo_connection_id=byo_connection_id,
108+
model_details=model_details,
106109
**model_kwargs,
107110
)
108111

@@ -138,6 +141,7 @@ def get_chat_model(
138141
settings=client_settings,
139142
api_flavor=api_flavor,
140143
byo_connection_id=byo_connection_id,
144+
model_details=model_details,
141145
**model_kwargs,
142146
)
143147
else:
@@ -150,6 +154,7 @@ def get_chat_model(
150154
settings=client_settings,
151155
api_flavor=api_flavor,
152156
byo_connection_id=byo_connection_id,
157+
model_details=model_details,
153158
**model_kwargs,
154159
)
155160
case VendorType.VERTEXAI:
@@ -163,6 +168,7 @@ def get_chat_model(
163168
settings=client_settings,
164169
vendor_type=discovered_vendor_type,
165170
byo_connection_id=byo_connection_id,
171+
model_details=model_details,
166172
**model_kwargs,
167173
)
168174

@@ -174,6 +180,7 @@ def get_chat_model(
174180
model=model_name,
175181
settings=client_settings,
176182
byo_connection_id=byo_connection_id,
183+
model_details=model_details,
177184
**model_kwargs,
178185
)
179186
case VendorType.AWSBEDROCK:
@@ -188,6 +195,7 @@ def get_chat_model(
188195
model=model_name,
189196
settings=client_settings,
190197
byo_connection_id=byo_connection_id,
198+
model_details=model_details,
191199
**model_kwargs,
192200
)
193201

@@ -200,6 +208,7 @@ def get_chat_model(
200208
model=model_name,
201209
settings=client_settings,
202210
byo_connection_id=byo_connection_id,
211+
model_details=model_details,
203212
**model_kwargs,
204213
)
205214

@@ -211,6 +220,7 @@ def get_chat_model(
211220
model=model_name,
212221
settings=client_settings,
213222
byo_connection_id=byo_connection_id,
223+
model_details=model_details,
214224
**model_kwargs,
215225
)
216226

packages/uipath_langchain_client/src/uipath_langchain_client/utils.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
UiPathTooManyRequestsError,
1414
UiPathUnprocessableEntityError,
1515
)
16-
from uipath.llm_client.utils.model_family import (
17-
ANTHROPIC_MODEL_NAME_KEYWORDS,
18-
is_anthropic_model_name,
19-
)
2016
from uipath.llm_client.utils.retry import RetryConfig
2117

2218
__all__ = [
@@ -34,6 +30,4 @@
3430
"UiPathServiceUnavailableError",
3531
"UiPathGatewayTimeoutError",
3632
"UiPathTooManyRequestsError",
37-
"ANTHROPIC_MODEL_NAME_KEYWORDS",
38-
"is_anthropic_model_name",
3933
]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LLM Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services."
3-
__version__ = "1.9.9"
3+
__version__ = "1.10.0"

0 commit comments

Comments
 (0)