Skip to content

Commit 2bb7c89

Browse files
Fix: Make reasoning_effort optional (#1004)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 41d8d80 commit 2bb7c89

File tree

4 files changed

+38
-22
lines changed

4 files changed

+38
-22
lines changed

openhands-sdk/openhands/sdk/llm/llm.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,17 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
213213
description="Whether to use native tool calling.",
214214
)
215215
reasoning_effort: Literal["low", "medium", "high", "none"] | None = Field(
216-
default=None,
216+
default="high",
217217
description="The effort to put into reasoning. "
218218
"This is a string that can be one of 'low', 'medium', 'high', or 'none'. "
219219
"Can apply to all reasoning models.",
220220
)
221+
reasoning_summary: Literal["auto", "concise", "detailed"] | None = Field(
222+
default=None,
223+
description="The level of detail for reasoning summaries. "
224+
"This is a string that can be one of 'auto', 'concise', or 'detailed'. "
225+
"Requires verified OpenAI organization. Only sent when explicitly set.",
226+
)
221227
enable_encrypted_reasoning: bool = Field(
222228
default=False,
223229
description="If True, ask for ['reasoning.encrypted_content'] "
@@ -312,14 +318,6 @@ def _coerce_inputs(cls, data):
312318
if not model_val:
313319
raise ValueError("model must be specified in LLM")
314320

315-
# default reasoning_effort unless Gemini 2.5
316-
# (we keep consistent with old behavior)
317-
excluded_models = ["gemini-2.5-pro", "claude-sonnet-4-5", "claude-haiku-4-5"]
318-
if d.get("reasoning_effort") is None and not any(
319-
model in model_val for model in excluded_models
320-
):
321-
d["reasoning_effort"] = "high"
322-
323321
# Azure default version
324322
if model_val.startswith("azure") and not d.get("api_version"):
325323
d["api_version"] = "2024-12-01-preview"

openhands-sdk/openhands/sdk/llm/options/responses_options.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ def select_responses_options(
4343
if include_list:
4444
out["include"] = include_list
4545

46-
# Request plaintext reasoning summary
47-
effort = llm.reasoning_effort or "high"
48-
out["reasoning"] = {"effort": effort, "summary": "detailed"}
46+
# Include reasoning effort only if explicitly set
47+
if llm.reasoning_effort:
48+
out["reasoning"] = {"effort": llm.reasoning_effort}
49+
# Optionally include summary if explicitly set (requires verified org)
50+
if llm.reasoning_summary:
51+
out["reasoning"]["summary"] = llm.reasoning_summary
4952

5053
# Pass through litellm_extra_body if provided
5154
if llm.litellm_extra_body:

tests/sdk/config/test_llm_config.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_llm_config_defaults():
3737
assert config.log_completions is False
3838
assert config.custom_tokenizer is None
3939
assert config.native_tool_calling is True
40-
assert config.reasoning_effort == "high" # Default for non-Gemini models
40+
assert config.reasoning_effort == "high"
4141
assert config.seed is None
4242
assert config.safety_settings is None
4343

@@ -170,13 +170,17 @@ def test_llm_config_post_init_openrouter_env_vars():
170170

171171

172172
def test_llm_config_post_init_reasoning_effort_default():
173-
"""Test that reasoning_effort is set to 'high' by default for non-Gemini models."""
173+
"""Test reasoning_effort defaults to high."""
174174
config = LLM(model="gpt-4", usage_id="test-llm")
175175
assert config.reasoning_effort == "high"
176176

177-
# Test that Gemini models don't get default reasoning_effort
177+
# Test that Gemini models also default to high
178178
config = LLM(model="gemini-2.5-pro-experimental", usage_id="test-llm")
179-
assert config.reasoning_effort is None
179+
assert config.reasoning_effort == "high"
180+
181+
# Test that explicit reasoning_effort is preserved
182+
config = LLM(model="gpt-4", reasoning_effort="low", usage_id="test-llm")
183+
assert config.reasoning_effort == "low"
180184

181185

182186
def test_llm_config_post_init_azure_api_version():
@@ -363,8 +367,6 @@ def test_llm_config_optional_fields():
363367
assert config.disable_vision is None
364368
assert config.disable_stop_word is None
365369
assert config.custom_tokenizer is None
366-
assert (
367-
config.reasoning_effort == "high"
368-
) # Even when set to None, post_init sets it to "high" for non-Gemini models
370+
assert config.reasoning_effort is None # Explicitly set to None overrides default
369371
assert config.seed is None
370372
assert config.safety_settings is None

tests/sdk/llm/test_responses_parsing_and_kwargs.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_from_llm_responses_output_parsing():
5858

5959

6060
def test_normalize_responses_kwargs_policy():
61-
llm = LLM(model="gpt-5-mini")
61+
llm = LLM(model="gpt-5-mini", reasoning_effort="high")
6262
# Use a model that is explicitly Responses-capable per model_features
6363

6464
# enable encrypted reasoning and set max_output_tokens to test passthrough
@@ -75,14 +75,27 @@ def test_normalize_responses_kwargs_policy():
7575
assert set(out["include"]) >= {"text.output_text", "reasoning.encrypted_content"}
7676
# store default to False when None passed
7777
assert out["store"] is False
78-
# reasoning config defaulted
78+
# reasoning config with effort only (no summary for unverified orgs)
7979
r = out["reasoning"]
8080
assert r["effort"] in {"low", "medium", "high", "none"}
81-
assert r["summary"] == "detailed"
81+
assert "summary" not in r # Summary not included to support unverified orgs
8282
# max_output_tokens preserved
8383
assert out["max_output_tokens"] == 128
8484

8585

86+
def test_normalize_responses_kwargs_with_summary():
87+
"""Test reasoning_summary is included when set (verified orgs)."""
88+
llm = LLM(model="gpt-5-mini", reasoning_effort="high", reasoning_summary="detailed")
89+
90+
out = select_responses_options(
91+
llm, {"temperature": 0.3}, include=["text.output_text"], store=None
92+
)
93+
# Verify reasoning includes both effort and summary when summary is set
94+
r = out["reasoning"]
95+
assert r["effort"] == "high"
96+
assert r["summary"] == "detailed"
97+
98+
8699
@patch("openhands.sdk.llm.llm.litellm_responses")
87100
def test_llm_responses_end_to_end(mock_responses_call):
88101
# Configure LLM

0 commit comments

Comments
 (0)