Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions openhands-sdk/openhands/sdk/llm/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
ge=0,
description=(
"Sampling temperature for response generation. "
"Defaults to 0 for most models and provider default for reasoning models."
"Defaults to None (uses provider default temperature). "
"Set to 0.0 for deterministic outputs, "
"or higher values (0.7-1.0) for more creative responses."
),
)
top_p: float | None = Field(default=1.0, ge=0, le=1)
Expand Down Expand Up @@ -461,7 +463,7 @@ def _set_env_side_effects(self):
self._init_model_info_and_caps()

if self.temperature is None:
self.temperature = get_default_temperature(self.model)
self.temperature = get_default_temperature()

logger.debug(
f"LLM ready: model={self.model} base_url={self.base_url} "
Expand Down
21 changes: 6 additions & 15 deletions openhands-sdk/openhands/sdk/llm/utils/model_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,20 +180,11 @@ def get_features(model: str) -> ModelFeatures:
)


# Default temperature mapping.
# Each entry: (pattern, default_temperature)
DEFAULT_TEMPERATURE_MODELS: list[tuple[str, float]] = [
("kimi-k2-thinking", 1.0),
("kimi-k2.5", 1.0),
]


def get_default_temperature(model: str) -> float:
"""Return the default temperature for a given model pattern.
def get_default_temperature() -> float | None:
"""Return the default temperature for a given model.

Uses case-insensitive substring matching via model_matches.
Returns None to use provider defaults.
Previously supported specific temperature overrides for certain models,
but now all models use provider defaults.
"""
for pattern, value in DEFAULT_TEMPERATURE_MODELS:
if model_matches(model, [pattern]):
return value
return 0.0
return None
2 changes: 1 addition & 1 deletion tests/sdk/config/test_llm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_llm_config_defaults():
assert config.retry_max_wait == 64
assert config.timeout == 300 # Default timeout is 5 minutes
assert config.max_message_chars == 30_000
assert config.temperature == 0.0
assert config.temperature is None # None to use provider defaults
assert config.top_p == 1.0
assert config.top_k is None
assert config.max_input_tokens == 128000 # Auto-populated from model info
Expand Down
2 changes: 1 addition & 1 deletion tests/sdk/llm/test_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ def test_llm_local_detection_based_on_model_name(default_llm):

# Test basic model configuration
assert llm.model == "gpt-4o"
assert llm.temperature == 0.0
assert llm.temperature is None # Uses provider default

# Test with localhost base_url
local_llm = default_llm.model_copy(update={"base_url": "http://localhost:8000"})
Expand Down
53 changes: 9 additions & 44 deletions tests/sdk/llm/test_model_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,52 +314,17 @@ def test_send_reasoning_content_support(model, expected_send_reasoning):
assert features.send_reasoning_content is expected_send_reasoning


@pytest.mark.parametrize(
"model,expected_temperature",
[
# kimi-k2-thinking models should default to 1.0
("kimi-k2-thinking", 1.0),
("kimi-k2-thinking-0905", 1.0),
("Kimi-K2-Thinking", 1.0), # Case insensitive
("moonshot/kimi-k2-thinking", 1.0), # With provider prefix
("litellm_proxy/kimi-k2-thinking", 1.0), # With litellm proxy prefix
# kimi-k2.5 models should also default to 1.0
("kimi-k2.5", 1.0),
("Kimi-K2.5", 1.0), # Case insensitive
# All other models should default to 0.0
("kimi-k2-instruct", 0.0), # Different kimi variant
("gpt-4", 0.0),
("gpt-4o", 0.0),
("gpt-4o-mini", 0.0),
("claude-3-5-sonnet", 0.0),
("claude-3-7-sonnet", 0.0),
("gemini-1.5-pro", 0.0),
("gemini-2.5-pro-experimental", 0.0),
("o1", 0.0),
("o1-mini", 0.0),
("o3", 0.0),
("deepseek-chat", 0.0),
("llama-3.1-70b", 0.0),
("azure/gpt-4o-mini", 0.0),
("openai/gpt-4o", 0.0),
("anthropic/claude-3-5-sonnet", 0.0),
("unknown-model", 0.0),
],
)
def test_get_default_temperature(model, expected_temperature):
"""Test that get_default_temperature returns correct values for different models."""
assert get_default_temperature(model) == expected_temperature
def test_get_default_temperature():
"""Test that get_default_temperature returns None."""
assert get_default_temperature() is None


def test_get_default_temperature_fallback():
"""Test that get_default_temperature returns 0.0 for unknown models."""
assert get_default_temperature("completely-unknown-model-12345") == 0.0
assert get_default_temperature("some-random-model") == 0.0
"""Test that get_default_temperature returns None."""
assert get_default_temperature() is None


def test_get_default_temperature_case_insensitive():
"""Test that get_default_temperature is case insensitive."""
assert get_default_temperature("kimi-k2-thinking") == 1.0
assert get_default_temperature("KIMI-K2-THINKING") == 1.0
assert get_default_temperature("Kimi-K2-Thinking") == 1.0
assert get_default_temperature("KiMi-k2-ThInKiNg") == 1.0
def test_get_default_temperature_returns_none():
"""Test that get_default_temperature returns None regardless of input."""
# All models now return None, so case doesn't matter
assert get_default_temperature() is None
Loading