Skip to content

fix: strip constructor-set sampling fields at construction time#83

Merged
cosminacho merged 3 commits into
mainfrom
fix/strip-disabled-fields-at-invoke
May 21, 2026
Merged

fix: strip constructor-set sampling fields at construction time#83
cosminacho merged 3 commits into
mainfrom
fix/strip-disabled-fields-at-invoke

Conversation

@cosminacho
Copy link
Copy Markdown
Collaborator

@cosminacho cosminacho commented May 20, 2026

Summary

  • UiPathChatAnthropicBedrock(model="anthropic.claude-opus-4-7", temperature=0.7) previously leaked the disabled field into the outgoing request body — langchain-anthropic and langchain-aws build payloads from self.<field> rather than the per-call kwargs, so the existing strip_disabled_kwargs filter never saw it. The gateway then rejected the call with 400 BadRequestError because modelDetails.shouldSkipTemperature: true for reasoning-style models.
  • Adds strip_disabled_fields in uipath.llm_client.utils.sampling — a sibling of strip_disabled_kwargs that eagerly nulls matching instance attributes. Called once from UiPathBaseLLMClient.setup_model_info right after disabled_params resolves, so the strip happens at construction time. A warning is logged per stripped field with the original value, so callers see exactly what was dropped (no silent surprises).
  • Plugs the init-time leak called out as the "Known follow-up" in langchain 1.10.0.

Why construction-time strip (not invoke-time)

The first iteration of this PR used a context manager that temporarily nulled matching fields for the duration of each call and restored them on exit. That preserved chat.temperature == 0.7 on the instance, but it was sneaky: the gateway will never accept that value, so reporting 0.7 was a lie that hid the truth from the user. The eager construction-time strip is honest (chat.temperature is None matches what the gateway sees), simpler (no save/restore, no exception edge cases, no generator wrapping for streams), and gives the user immediate feedback via the warning rather than letting them be surprised at invoke time.

Packages affected

Both — touches core (utils/sampling.py) and langchain (base_client.py):

  • Core: 1.11.0 -> 1.11.2 (exposes the new helper)
  • Langchain: 1.11.1 -> 1.11.2 (wiring + dep-floor bump to >=1.11.2)

Tests

  • 6 new unit tests in tests/langchain/test_disabled_sampling_params.py pin down: field nulled when flag set; not touched when flag absent; value-list spec respected; default-None fields not warned about; warning includes the original value; silent when logger=None.
  • 2 new VCR-cassetted tests in tests/langchain/test_disabled_sampling_params_integration.py exercise the fix end-to-end against anthropic.claude-opus-4-7 for both vendor SDK families (UiPathChatAnthropicBedrock -> langchain-anthropic, UiPathChatBedrockConverse -> langchain-aws). The recorded 200 response is itself proof of the fix — before_record_response in tests/conftest.py drops 4xx/5xx, so a pre-fix recording would have refused to persist a cassette.

Test plan

  • ruff check && ruff format . --check — clean
  • pyright — 0 errors
  • pytest tests — 1560 passed, 0 failed
  • Verified manually against alpha LLM Gateway with UiPathChatAnthropicBedrock, UiPathChatBedrockConverse, and the INVOKE-flavor dispatch — all three succeed with constructor-set temperature=0.7.

🤖 Generated with Claude Code

@cosminacho cosminacho changed the title fix: strip constructor-set sampling fields at invoke time fix: strip constructor-set sampling fields at construction time May 20, 2026
cosminacho and others added 3 commits May 21, 2026 11:24
`UiPathChatAnthropicBedrock(model="anthropic.claude-opus-4-7", temperature=0.7)`
previously leaked the disabled field into the request body because langchain-
anthropic and langchain-aws build payloads from `self.<field>`, bypassing the
existing kwargs-level `strip_disabled_kwargs`. The gateway then rejected the
call with 400 (modelDetails.shouldSkipTemperature: true for reasoning models).

Adds `disabled_fields_stripped` context manager in core sampling utils — a
sibling of `strip_disabled_kwargs` that temporarily nulls matching instance
attributes for the duration of the underlying call and restores them on exit.
Wired into the four `_generate`/`_agenerate`/`_stream`/`_astream` wrappers in
`UiPathBaseChatModel`, so caller-visible state (`chat.temperature`) is
unchanged but the vendor SDK reads `None` while building the request.

Plugs the init-time leak called out as a known follow-up in 1.10.0.

Tests:
- 7 new unit tests pin down the during-call/after-call semantics, exception
  restoration, value-list spec handling, and warning logging.
- 2 new VCR-cassetted integration tests against `anthropic.claude-opus-4-7`
  exercise both vendor SDK families (anthropic-bedrock + bedrock-converse).
  The recorded 200 is itself proof of the fix — `before_record_response`
  drops 4xx, so a pre-fix run would have refused to persist the cassette.

Core 1.11.0 -> 1.11.2 (new helper exposed)
Langchain 1.11.1 -> 1.11.2 (wiring + dep floor bump)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t manager

Prior approach used a context manager that temporarily nulled matching fields
for the duration of each call and restored them on exit, so `chat.temperature`
read the caller's original value between invocations. That's sneaky: the
gateway will never accept the value, so reporting 0.7 on the instance is a lie
that hides the truth from the user.

Replace `disabled_fields_stripped` (context manager) with `strip_disabled_fields`
(eager, one-shot). Wire it into `UiPathBaseLLMClient.setup_model_info` so the
strip happens once, immediately after `disabled_params` resolves. Each strip
logs a warning that includes the original value, so the caller sees exactly
what was dropped at construction time rather than being silently surprised
later. The four `_generate`/`_agenerate`/`_stream`/`_astream` wrappers are now
plain again — no context-manager wrapping, no save/restore, no exception edge
cases, no generator subtlety.

Net effect for callers:
- `UiPathChatAnthropicBedrock(model="anthropic.claude-opus-4-7", temperature=0.7)`
  now logs a warning and leaves `chat.temperature is None`.
- Per-call `chat.invoke(..., temperature=0.7)` still gets stripped by the
  existing `strip_disabled_kwargs` filter, unchanged.

Tests updated to assert the field is None after construction (not "restored
after call"). Restore-on-exception test removed (no longer applicable). VCR
cassettes still replay — the gateway request body is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous object.__setattr__ was defensive bypass-validation, but every
field in DISABLED_SAMPLING_PARAMS is declared as `<type> | None = None` on the
underlying langchain subclasses, so None is always valid. `strip_disabled_fields`
runs inside `@model_validator(mode="after")` and operates on the constructed
pydantic model, so plain setattr already goes through `BaseModel.__setattr__`
and respects field types. If a future caller tries to disable a non-nullable
custom field, raising is the right behavior — better than silently bypassing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cosminacho cosminacho force-pushed the fix/strip-disabled-fields-at-invoke branch from 7524161 to 8d267e9 Compare May 21, 2026 08:26
@cosminacho cosminacho merged commit ff7262e into main May 21, 2026
10 checks passed
@cosminacho cosminacho deleted the fix/strip-disabled-fields-at-invoke branch May 21, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants