Skip to content

Commit d190868

Browse files
AAgnihotryclaude
andauthored
fix(llm): exclude sampling params for reasoning models (#1822)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 487bf03 commit d190868

6 files changed

Lines changed: 246 additions & 13 deletions

File tree

packages/uipath-platform/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-platform"
3-
version = "0.2.10"
3+
version = "0.2.11"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/chat/_llm_gateway_service.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,17 @@ class Country(BaseModel):
327327
)
328328
endpoint = Endpoint("/" + endpoint)
329329

330-
request_body = {
330+
is_reasoning_model = model.lower().startswith(("o1", "o3", "o4"))
331+
332+
request_body: dict[str, Any] = {
331333
"messages": messages,
332334
"max_tokens": max_tokens,
333-
"temperature": temperature,
334335
}
335336

337+
# Reasoning models (o1, o3, o4) don't support temperature
338+
if not is_reasoning_model:
339+
request_body["temperature"] = temperature
340+
336341
# Handle response_format - convert BaseModel to schema if needed
337342
if response_format:
338343
if isinstance(response_format, type) and issubclass(
@@ -548,17 +553,22 @@ class Country(BaseModel):
548553
)
549554
endpoint = Endpoint("/" + endpoint)
550555

551-
# Build request body - Claude models don't support some OpenAI-specific parameters
552-
is_claude_model = "claude" in model.lower()
556+
# Build request body - some models don't support certain parameters
557+
model_lower = model.lower()
558+
is_claude_model = "claude" in model_lower
559+
is_reasoning_model = model_lower.startswith(("o1", "o3", "o4"))
553560

554-
request_body = {
561+
request_body: dict[str, Any] = {
555562
"messages": converted_messages,
556563
"max_tokens": max_tokens,
557-
"temperature": temperature,
558564
}
559565

560-
# Only add OpenAI-specific parameters for non-Claude models
561-
if not is_claude_model:
566+
# Reasoning models (o1, o3, o4) don't support temperature and sampling parameters
567+
if not is_reasoning_model:
568+
request_body["temperature"] = temperature
569+
570+
# Only add OpenAI-specific parameters for non-Claude and non-reasoning models
571+
if not is_claude_model and not is_reasoning_model:
562572
request_body["n"] = n
563573
request_body["frequency_penalty"] = frequency_penalty
564574
request_body["presence_penalty"] = presence_penalty

packages/uipath-platform/tests/services/test_llm_service.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,104 @@ class Article(BaseModel):
384384
assert article_instance.title is None
385385

386386

387+
class TestOpenAIServiceReasoningModelFiltering:
388+
"""Test that UiPathOpenAIService correctly filters out temperature for reasoning models."""
389+
390+
@pytest.fixture
391+
def config(self):
392+
return UiPathApiConfig(base_url="https://example.com", secret="test_secret")
393+
394+
@pytest.fixture
395+
def execution_context(self):
396+
return UiPathExecutionContext()
397+
398+
@pytest.fixture
399+
def openai_service(self, config, execution_context):
400+
return UiPathOpenAIService(config=config, execution_context=execution_context)
401+
402+
@pytest.mark.parametrize(
403+
"model",
404+
[
405+
"o3-mini-2025-01-31",
406+
"o4-mini-2025-04-16",
407+
"o1-2024-12-17",
408+
"o1-mini-2024-09-12",
409+
"o3-2025-04-16",
410+
],
411+
)
412+
@patch.object(UiPathOpenAIService, "request_async")
413+
@pytest.mark.asyncio
414+
async def test_reasoning_model_excludes_temperature(
415+
self, mock_request, openai_service, model
416+
):
417+
"""Test that reasoning models do not include temperature in the request body."""
418+
mock_response = MagicMock()
419+
mock_response.json.return_value = {
420+
"id": "chatcmpl-test",
421+
"object": "chat.completion",
422+
"created": 1234567890,
423+
"model": model,
424+
"choices": [
425+
{
426+
"index": 0,
427+
"message": {"role": "assistant", "content": "Hello"},
428+
"finish_reason": "stop",
429+
}
430+
],
431+
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
432+
}
433+
mock_request.return_value = mock_response
434+
435+
await openai_service.chat_completions(
436+
messages=[{"role": "user", "content": "Hello"}],
437+
model=model,
438+
max_tokens=1000,
439+
)
440+
441+
call_kwargs = mock_request.call_args[1]
442+
request_body = call_kwargs["json"]
443+
444+
assert "temperature" not in request_body, (
445+
f"Reasoning model {model} request must not include 'temperature'"
446+
)
447+
assert request_body["max_tokens"] == 1000
448+
449+
@patch.object(UiPathOpenAIService, "request_async")
450+
@pytest.mark.asyncio
451+
async def test_non_reasoning_model_includes_temperature(
452+
self, mock_request, openai_service
453+
):
454+
"""Test that non-reasoning models still include temperature."""
455+
mock_response = MagicMock()
456+
mock_response.json.return_value = {
457+
"id": "chatcmpl-test",
458+
"object": "chat.completion",
459+
"created": 1234567890,
460+
"model": "gpt-4.1-mini-2025-04-14",
461+
"choices": [
462+
{
463+
"index": 0,
464+
"message": {"role": "assistant", "content": "Hello"},
465+
"finish_reason": "stop",
466+
}
467+
],
468+
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
469+
}
470+
mock_request.return_value = mock_response
471+
472+
await openai_service.chat_completions(
473+
messages=[{"role": "user", "content": "Hello"}],
474+
model="gpt-4.1-mini-2025-04-14",
475+
max_tokens=1000,
476+
temperature=0.7,
477+
)
478+
479+
call_kwargs = mock_request.call_args[1]
480+
request_body = call_kwargs["json"]
481+
482+
assert request_body["temperature"] == 0.7
483+
484+
387485
class TestNormalizedLlmServiceClaudeFiltering:
388486
"""Test that Claude models correctly filter out OpenAI-specific parameters.
389487
@@ -543,3 +641,128 @@ async def test_claude_sonnet_45_excluded_params(self, mock_request, llm_service)
543641
assert "presence_penalty" not in request_body
544642
assert "top_p" not in request_body
545643
assert request_body["max_tokens"] == 8000
644+
645+
646+
class TestNormalizedLlmServiceReasoningModelFiltering:
647+
"""Test that reasoning models (o1, o3, o4) correctly filter out unsupported sampling parameters.
648+
649+
OpenAI reasoning models do NOT support temperature, top_p, frequency_penalty,
650+
presence_penalty, or n, and sending them causes 400 errors.
651+
"""
652+
653+
@pytest.fixture
654+
def config(self):
655+
return UiPathApiConfig(base_url="https://example.com", secret="test_secret")
656+
657+
@pytest.fixture
658+
def execution_context(self):
659+
return UiPathExecutionContext()
660+
661+
@pytest.fixture
662+
def llm_service(self, config, execution_context):
663+
from uipath.platform.chat._llm_gateway_service import UiPathLlmChatService
664+
665+
return UiPathLlmChatService(config=config, execution_context=execution_context)
666+
667+
@pytest.mark.parametrize(
668+
"model",
669+
[
670+
"o3-mini-2025-01-31",
671+
"o4-mini-2025-04-16",
672+
"o1-2024-12-17",
673+
"o1-mini-2024-09-12",
674+
"o3-2025-04-16",
675+
],
676+
)
677+
@patch(
678+
"uipath.platform.chat._llm_gateway_service.UiPathLlmChatService.request_async"
679+
)
680+
@pytest.mark.asyncio
681+
async def test_reasoning_model_excludes_sampling_params(
682+
self, mock_request, llm_service, model
683+
):
684+
"""Test that reasoning models do not include temperature or other sampling params."""
685+
mock_response = MagicMock()
686+
mock_response.status_code = 200
687+
mock_response.json.return_value = {
688+
"id": "chatcmpl-test",
689+
"object": "chat.completion",
690+
"created": 1234567890,
691+
"model": model,
692+
"choices": [
693+
{
694+
"index": 0,
695+
"message": {"role": "assistant", "content": "Hello"},
696+
"finish_reason": "stop",
697+
}
698+
],
699+
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
700+
}
701+
mock_request.return_value = mock_response
702+
703+
await llm_service.chat_completions(
704+
messages=[{"role": "user", "content": "Hello"}],
705+
model=model,
706+
max_tokens=1000,
707+
)
708+
709+
call_kwargs = mock_request.call_args[1]
710+
request_body = call_kwargs["json"]
711+
712+
assert "temperature" not in request_body, (
713+
f"Reasoning model {model} request must not include 'temperature'"
714+
)
715+
assert "n" not in request_body, (
716+
f"Reasoning model {model} request must not include 'n'"
717+
)
718+
assert "frequency_penalty" not in request_body, (
719+
f"Reasoning model {model} request must not include 'frequency_penalty'"
720+
)
721+
assert "presence_penalty" not in request_body, (
722+
f"Reasoning model {model} request must not include 'presence_penalty'"
723+
)
724+
assert "top_p" not in request_body, (
725+
f"Reasoning model {model} request must not include 'top_p'"
726+
)
727+
assert request_body["max_tokens"] == 1000
728+
729+
@patch(
730+
"uipath.platform.chat._llm_gateway_service.UiPathLlmChatService.request_async"
731+
)
732+
@pytest.mark.asyncio
733+
async def test_non_reasoning_model_includes_temperature(
734+
self, mock_request, llm_service
735+
):
736+
"""Test that non-reasoning models still include temperature and sampling params."""
737+
mock_response = MagicMock()
738+
mock_response.status_code = 200
739+
mock_response.json.return_value = {
740+
"id": "chatcmpl-test",
741+
"object": "chat.completion",
742+
"created": 1234567890,
743+
"model": "gpt-4.1-mini-2025-04-14",
744+
"choices": [
745+
{
746+
"index": 0,
747+
"message": {"role": "assistant", "content": "Hello"},
748+
"finish_reason": "stop",
749+
}
750+
],
751+
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
752+
}
753+
mock_request.return_value = mock_response
754+
755+
await llm_service.chat_completions(
756+
messages=[{"role": "user", "content": "Hello"}],
757+
model="gpt-4.1-mini-2025-04-14",
758+
max_tokens=1000,
759+
temperature=0.5,
760+
)
761+
762+
call_kwargs = mock_request.call_args[1]
763+
request_body = call_kwargs["json"]
764+
765+
assert request_body["temperature"] == 0.5
766+
assert "n" in request_body
767+
assert "frequency_penalty" in request_body
768+
assert "presence_penalty" in request_body

packages/uipath-platform/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.

packages/uipath/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"
3-
version = "2.13.12"
3+
version = "2.13.13"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath/uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)