Skip to content

Commit 7d2bc95

Browse files
cosminachoclaude
andcommitted
Refactor: use model_fields_set to exclude unset UiPathChat params
Replace the _UNSET sentinel approach with pydantic's built-in model_fields_set. Fields keep plain `int | None = None` defaults; _default_params includes only fields that were explicitly set by the caller. Explicit None passes through to the API as null. No sentinel class, no type: ignore, no extra model_config needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 892fc3f commit 7d2bc95

2 files changed

Lines changed: 25 additions & 30 deletions

File tree

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to `uipath_langchain_client` will be documented in this file
55
## [1.9.7] - 2026-04-22
66

77
### Changed
8-
- **Behavior change:** `UiPathChat` parameter fields now default to an internal `_UNSET` sentinel instead of `None`, and `_default_params` filters on the sentinel rather than on `None`. Unset fields are still omitted from the request payload, but passing an explicit `None` (e.g. `UiPathChat(temperature=None)`) now forwards `null` to the normalized API instead of being silently dropped. Previously, both "not passed" and "explicitly `None`" produced the same omission. Callers relying on `None` as shorthand for "omit" should switch to simply not passing the argument.
8+
- **Behavior change:** `UiPathChat._default_params` now uses pydantic's `model_fields_set` to decide which params to include in the request payload instead of filtering on `v is not None`. Fields that were not explicitly passed are omitted; fields explicitly set to `None` (e.g. `UiPathChat(temperature=None)`) now forward `null` to the API. Previously both "not passed" and "explicitly `None`" were silently dropped.
99

1010
## [1.9.6] - 2026-04-22
1111

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

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
convert_to_openai_tool,
6363
)
6464
from langchain_core.utils.pydantic import is_basemodel_subclass
65-
from pydantic import AliasChoices, BaseModel, ConfigDict, Field
65+
from pydantic import AliasChoices, BaseModel, Field
6666

6767
from uipath_langchain_client.base_client import UiPathBaseChatModel
6868
from uipath_langchain_client.settings import ApiType, RoutingMode, UiPathAPIConfig
@@ -72,10 +72,6 @@
7272
_DictOrPydantic = Union[dict[str, Any], BaseModel]
7373

7474

75-
# Sentinel — params whose value is still _UNSET are omitted from the request payload.
76-
_UNSET: Any = object()
77-
78-
7975
def _oai_structured_outputs_parser(ai_msg: AIMessage, schema: type[BaseModel]) -> BaseModel:
8076
if not ai_msg.content:
8177
raise ValueError("Expected non-empty content from model.")
@@ -162,50 +158,48 @@ class UiPathChat(UiPathBaseChatModel):
162158
freeze_base_url=True,
163159
)
164160

165-
model_config = ConfigDict(validate_default=False)
166-
167161
# Common
168162
max_tokens: int | None = Field(
169-
default=_UNSET,
163+
default=None,
170164
validation_alias=AliasChoices("max_tokens", "max_output_tokens", "max_completion_tokens"),
171165
)
172-
temperature: float | None = _UNSET # type: ignore[assignment]
173-
top_p: float | None = _UNSET # type: ignore[assignment]
174-
top_k: int | None = _UNSET # type: ignore[assignment]
166+
temperature: float | None = None
167+
top_p: float | None = None
168+
top_k: int | None = None
175169
stop: list[str] | str | None = Field(
176-
default=_UNSET,
170+
default=None,
177171
validation_alias=AliasChoices("stop", "stop_sequences"),
178172
)
179173
n: int | None = Field(
180-
default=_UNSET,
174+
default=None,
181175
validation_alias=AliasChoices("n", "candidate_count"),
182176
)
183-
frequency_penalty: float | None = _UNSET # type: ignore[assignment]
184-
presence_penalty: float | None = _UNSET # type: ignore[assignment]
185-
seed: int | None = _UNSET # type: ignore[assignment]
177+
frequency_penalty: float | None = None
178+
presence_penalty: float | None = None
179+
seed: int | None = None
186180

187181
model_kwargs: dict[str, Any] = Field(default_factory=dict)
188182
disabled_params: dict[str, Any] | None = None
189183

190184
# OpenAI
191-
logit_bias: dict[str, int] | None = _UNSET # type: ignore[assignment]
192-
logprobs: bool | None = _UNSET # type: ignore[assignment]
193-
top_logprobs: int | None = _UNSET # type: ignore[assignment]
194-
parallel_tool_calls: bool | None = _UNSET # type: ignore[assignment]
195-
reasoning_effort: str | None = _UNSET # type: ignore[assignment]
196-
reasoning: dict[str, Any] | None = _UNSET # type: ignore[assignment]
185+
logit_bias: dict[str, int] | None = None
186+
logprobs: bool | None = None
187+
top_logprobs: int | None = None
188+
parallel_tool_calls: bool | None = None
189+
reasoning_effort: str | None = None
190+
reasoning: dict[str, Any] | None = None
197191

198192
# Anthropic
199-
thinking: dict[str, Any] | None = _UNSET # type: ignore[assignment]
193+
thinking: dict[str, Any] | None = None
200194

201195
# Google
202-
thinking_level: str | None = _UNSET # type: ignore[assignment]
203-
thinking_budget: int | None = _UNSET # type: ignore[assignment]
204-
include_thoughts: bool | None = _UNSET # type: ignore[assignment]
205-
safety_settings: list[dict[str, Any]] | None = _UNSET # type: ignore[assignment]
196+
thinking_level: str | None = None
197+
thinking_budget: int | None = None
198+
include_thoughts: bool | None = None
199+
safety_settings: list[dict[str, Any]] | None = None
206200

207201
# Shared
208-
verbosity: str | None = _UNSET # type: ignore[assignment]
202+
verbosity: str | None = None
209203

210204
@property
211205
def _llm_type(self) -> str:
@@ -248,8 +242,9 @@ def _default_params(self) -> dict[str, Any]:
248242
"verbosity": self.verbosity,
249243
}
250244

245+
set_fields = self.model_fields_set
251246
return {
252-
**{k: v for k, v in candidates.items() if v is not _UNSET},
247+
**{k: v for k, v in candidates.items() if k in set_fields},
253248
**self.model_kwargs,
254249
}
255250

0 commit comments

Comments
 (0)