Skip to content

fix(platform): drop temperature for models that reject it - #1834

Merged
Chibionos merged 3 commits into
mainfrom
fix/skip-temperature-unsupported-models
Jul 30, 2026
Merged

fix(platform): drop temperature for models that reject it#1834
Chibionos merged 3 commits into
mainfrom
fix/skip-temperature-unsupported-models

Conversation

@Chibionos

Copy link
Copy Markdown
Contributor

Problem

LLM-as-judge evals on claude-sonnet-5 fail, while agent executions on the same model succeed:

POST .../agenthub_/llm/api/chat/completions → 400
{"error":{"message":"`temperature` is deprecated for this model."}}

Root cause

LLM Gateway discovery publishes a per-model capability flag, modelDetails.shouldSkipTemperature.

  • The agent runtime honors it. uipath-langchain-python (chat_model_factory.py) resolves effective_temperature = None if _should_skip_temperature(model_info) else temperature, so agent calls omit the parameter and pass.
  • This SDK never read it. Both chat_completions paths put temperature in the body unconditionally, so every judge call on such a model 400s. grep shouldSkipTemperature across the repo returned nothing before this PR.

The existing guards were model-name heuristics ("claude" in model, startswith(("o1","o3","o4"))), so each new model family breaks a path until someone adds another string match. Nothing regressed in this repo — Sonnet 5 simply arrived in discovery with the flag set, and the judge violated a contract it never read.

Staging telemetry over 24h: all 137 normalized-route 400s carried x-uipath-agenthub-config: agentsevals. None came from agent execution. In one trace the LangGraph agent completed against anthropic.claude-sonnet-5 (0 of 47 calls sent temperature) while the evaluator's call in the same trace 400'd.

Fix

_model_capabilities.should_skip_temperature() resolves the flag from discovery, cached per (base_url, agenthub_config) — one GET per process, not per call. Both chat_completions paths omit temperature when the model reports it.

Fail-open by design: unknown models, unreachable discovery, and any unexpected error keep the caller's parameters, so behavior is unchanged everywhere the flag is not set. A discovery outage can never fail an LLM call.

Discovery resolves through EndpointManager.get_discovery_endpoint() rather than a hardcoded path, because the gateway is reachable via agenthub_/ or orchestrator_/.

Tests

tests/services/test_llm_temperature_skip.py — 7 cases: flag set / flag absent / model absent from discovery / discovery unreachable / cached across calls / scoped by agenthub config / OpenAI-compatible path.

Three fail against pre-fix code on the exact assertion:

AssertionError: assert 'temperature' not in {'max_tokens': 4096, 'messages': [...], 'temperature': 0}
Result
uipath-platform 1605 passed, 7 skipped (pre-existing, credential-gated)
uipath 2336 passed
ruff check / format, mypy — both packages clean

test_input_mocker.py gained a discovery mock: it asserts every HTTP request is expected, and the code now legitimately calls discovery. That is the only test in the repo coupled to the request set.

Releases

uipath-platform 0.2.13 → 0.2.14 (the fix), uipath 2.13.18 → 2.13.19.

uipath's floor moves to uipath-platform>=0.2.14 so no install of uipath can resolve a uipath-platform that still sends the parameter. Without it the constraint (>=0.2.4) would let a stale lockfile keep the broken version — which is how an earlier fix on this same path shipped but never reached python-eval-worker. Both uv.lock files regenerated; check_version_uniqueness and check_dependency_version_bumps pass locally.

Follow-up (not in this PR)

agents/backend/External.Clients/LlmGateway/LlmGatewayClient.cs:274 sends Temperature = settings?.Temperature ?? 0.0f on the same normalized route, and LlmModelMapper drops ShouldSkipTemperature off the discovery DTO even though DiscoveryModels.cs:36 defines it. No 400s from that path yet — no low-code agent has run on a skip-temperature model — but it will break the first time one does.

AgentHubService's _available_models_spec hardcodes /agenthub_/llm/api/discovery, so it breaks in orchestrator-routed deployments. Left alone to keep this diff surgical.

🤖 Generated with Claude Code

https://claude.ai/code/session_018nEN5r7dtNaEkN9y5ygXCT

Chibionos and others added 2 commits July 29, 2026 12:08
LLM-as-judge evals on claude-sonnet-5 failed with HTTP 400 "`temperature` is
deprecated for this model" while agent executions on the same model passed.

LLM Gateway discovery publishes modelDetails.shouldSkipTemperature per model.
uipath-langchain-python honors it, so agent runs omit the parameter. Both
chat_completions paths here always sent it, so every judge call on such a model
failed. In staging, all 137 normalized-route 400s over 24h carried
x-uipath-agenthub-config: agentsevals; none came from agent execution.

Resolve the flag from discovery (cached per base_url + agenthub config) and omit
temperature when the model reports it. Unknown models and unreachable discovery
keep the caller's parameters, so behaviour is unchanged wherever the flag is not
set. Discovery is resolved through EndpointManager so it follows the same
agenthub_/orchestrator_ routing as the completion it describes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018nEN5r7dtNaEkN9y5ygXCT
Ship the temperature fix and make it unskippable downstream: raise uipath's
lower bound on uipath-platform to 0.2.14 so no install of uipath can resolve a
uipath-platform that still sends temperature to models that reject it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018nEN5r7dtNaEkN9y5ygXCT
Copilot AI review requested due to automatic review settings July 29, 2026 19:42
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime test:uipath-integrations labels Jul 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29fc56c45b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +61 to +62
if key not in _model_details:
_model_details[key] = await _fetch_model_details(service, agenthub_config)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retry discovery after a failed fetch

When the first discovery request for a cache key is transiently unavailable or returns an invalid payload, _fetch_model_details returns {}, which is stored here as a successful process-lifetime result. Every later call for a model such as Sonnet 5 therefore keeps sending the rejected temperature parameter even after discovery recovers, so a brief outage can break all subsequent completions until process restart. Avoid caching failed/invalid reads, or expire and retry them.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the UiPath Python SDK’s LLM Gateway clients to honor a per-model discovery capability (modelDetails.shouldSkipTemperature) so requests omit temperature for models that reject it (e.g., claude-sonnet-5), preventing 400s in eval/judge flows while preserving fail-open behavior when discovery is unavailable.

Changes:

  • Add a cached discovery-based capability resolver and use it to conditionally drop temperature in both normalized and OpenAI-compatible chat_completions paths.
  • Extend endpoint routing support with discovery endpoints (agenthub_/... and orchestrator_/...) via EndpointManager.
  • Add regression tests for discovery-driven temperature skipping and bump uipath-platform/uipath versions + dependency floor to ensure the fix is pulled in.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/uipath/uv.lock Updates locked versions for uipath and uipath-platform.
packages/uipath/tests/cli/eval/mocks/test_input_mocker.py Updates HTTPX mocks to account for the new discovery call in eval input generation.
packages/uipath/pyproject.toml Bumps uipath version and raises minimum uipath-platform to include the fix.
packages/uipath-platform/uv.lock Updates locked version for uipath-platform.
packages/uipath-platform/tests/services/test_llm_temperature_skip.py Adds coverage for discovery-driven temperature omission (including caching and agenthub-config scoping).
packages/uipath-platform/src/uipath/platform/common/_endpoints_manager.py Adds discovery endpoint constants and a selector method consistent with other LLM endpoints.
packages/uipath-platform/src/uipath/platform/chat/_model_capabilities.py Implements cached discovery fetch + should_skip_temperature() helper (fail-open).
packages/uipath-platform/src/uipath/platform/chat/_llm_gateway_service.py Uses discovery capability to omit temperature for models that reject it (both chat paths).
packages/uipath-platform/pyproject.toml Bumps uipath-platform version to ship the fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +76
# Chat completions consults discovery to learn which parameters the model accepts.
httpx_mock.add_response(
url="https://example.com/llm/api/discovery",
status_code=200,
json=[{"modelName": "gpt-4o-mini-2024-07-18", "modelDetails": {}}],
)

# Reasoning models (o1, o3, o4) don't support temperature; newer models
# reject it too, which only discovery knows about.
skip_temperature = is_reasoning_model or await should_skip_temperature(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove is_reasining_model check here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, but I'd keep it — and it can't be deleted outright anyway.

Two reasons to keep it in the or:

  1. should_skip_temperature fails open by design. It returns False for an unknown model, unreachable discovery, or any unexpected exception. If is_reasoning_model comes out of the expression, a discovery outage means o1/o3/o4 start receiving temperature again — reintroducing exactly the 400 this PR exists to fix, for models that were previously safe. It gets worse combined with the caching behaviour I flagged separately: _details_for caches the failure, so one blip at process start would keep reasoning models 400ing for the whole process lifetime.

  2. It short-circuits. Python or means a known reasoning model never makes the discovery call at all — no round-trip, no latency, no dependency.

There's also an unverified assumption buried in the removal: that gateway discovery actually reports shouldSkipTemperature: true for o1/o3/o4. Worth checking empirically, but even if it does, point 1 still argues for keeping the local guard as the deterministic floor.

And separately — the variable itself is load-bearing elsewhere: line 584, if not is_claude_model and not is_reasoning_model:, gates n, frequency_penalty, presence_penalty and top_p. So is_reasoning_model has to stay regardless; the only question was whether to drop it from the skip_temperature expression.

Net: the local check is a cheap deterministic floor, discovery is the extension for models we can't name ahead of time. Belt and braces is right here.

@AAgnihotry AAgnihotry left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left 1 comment

@Chibionos
Chibionos enabled auto-merge (squash) July 30, 2026 15:47
@Chibionos

Copy link
Copy Markdown
Contributor Author

Auto-merge enabled (approved, checks green after a branch update from main).

One follow-up that should land before this is cherry-picked to a release branch, flagged in review and not blocking the merge to main:

_details_for caches the failure result. _fetch_model_details returns {} on any exception, and because the key is then present, if key not in _model_details never re-fetches. So a single discovery blip at process start permanently disables temperature-skipping for the life of that process — and Sonnet 5 evals keep returning the same 400 this PR exists to fix, until a restart.

The module comment does name the behaviour ("an empty mapping caches 'couldn't read discovery'"), so it reads as deliberate — but the consequence for a hotfix aimed at exactly that 400 is worth a second look. Two small options: only cache successful results, or give cached entries a TTL.

Merging anyway because it is a strict improvement on the status quo (today Sonnet 5 is 100% broken; after this it is broken only after a transient discovery failure). Raising it here so it is not lost before the release cherry-pick.

@sonarqubecloud

Copy link
Copy Markdown

@Chibionos
Chibionos merged commit 110e21e into main Jul 30, 2026
130 of 131 checks passed
@Chibionos
Chibionos deleted the fix/skip-temperature-unsupported-models branch July 30, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:uipath-integrations test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants