Skip to content

Commit 8262478

Browse files
committed
feat: support OPENAI_REASONING_EFFORT environment variable
Fixes #2686 Added utilities for configuring reasoning effort via environment variable: - get_reasoning_effort_from_env(): Read effort from OPENAI_REASONING_EFFORT - get_default_reasoning(): Build Reasoning config with env var support Valid values: none, minimal, low, medium, high, xhigh Precedence: 1. Explicit effort parameter 2. OPENAI_REASONING_EFFORT env var 3. SDK default (None) Example: export OPENAI_REASONING_EFFORT=low from openai.lib import get_default_reasoning reasoning = get_default_reasoning() # {'effort': 'low'}
1 parent f94256d commit 8262478

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

src/openai/lib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from ._tools import pydantic_function_tool as pydantic_function_tool
22
from ._parsing import ResponseFormatT as ResponseFormatT
3+
from ._reasoning import get_default_reasoning as get_default_reasoning
4+
from ._reasoning import get_reasoning_effort_from_env as get_reasoning_effort_from_env

src/openai/lib/_reasoning.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""Utilities for reasoning configuration.
2+
3+
This module provides utilities for configuring reasoning behavior,
4+
including support for the OPENAI_REASONING_EFFORT environment variable.
5+
6+
Example usage:
7+
from openai import OpenAI
8+
from openai.lib import get_default_reasoning
9+
10+
client = OpenAI()
11+
12+
# Uses OPENAI_REASONING_EFFORT env var if set, otherwise returns None
13+
reasoning = get_default_reasoning()
14+
15+
response = client.responses.create(
16+
model="gpt-5",
17+
input="Hello",
18+
reasoning=reasoning,
19+
)
20+
"""
21+
22+
from __future__ import annotations
23+
24+
import os
25+
import warnings
26+
from typing import Optional, Literal
27+
28+
from ..types.shared_params.reasoning import Reasoning
29+
from ..types.shared.reasoning_effort import ReasoningEffort
30+
31+
32+
__all__ = [
33+
"get_default_reasoning",
34+
"get_reasoning_effort_from_env",
35+
]
36+
37+
# Valid reasoning effort values
38+
VALID_REASONING_EFFORTS: tuple[str, ...] = (
39+
"none",
40+
"minimal",
41+
"low",
42+
"medium",
43+
"high",
44+
"xhigh",
45+
)
46+
47+
# Environment variable name
48+
REASONING_EFFORT_ENV_VAR = "OPENAI_REASONING_EFFORT"
49+
50+
51+
def get_reasoning_effort_from_env() -> Optional[ReasoningEffort]:
52+
"""Get reasoning effort from the OPENAI_REASONING_EFFORT environment variable.
53+
54+
Returns:
55+
The reasoning effort value if set and valid, None otherwise.
56+
57+
Valid values are: none, minimal, low, medium, high, xhigh
58+
59+
If an invalid value is set, a warning is emitted and None is returned.
60+
61+
Example:
62+
>>> import os
63+
>>> os.environ["OPENAI_REASONING_EFFORT"] = "low"
64+
>>> get_reasoning_effort_from_env()
65+
'low'
66+
"""
67+
value = os.environ.get(REASONING_EFFORT_ENV_VAR)
68+
if value is None:
69+
return None
70+
71+
# Normalize to lowercase
72+
value_lower = value.lower().strip()
73+
74+
if value_lower not in VALID_REASONING_EFFORTS:
75+
warnings.warn(
76+
f"Invalid {REASONING_EFFORT_ENV_VAR} value: '{value}'. "
77+
f"Valid values are: {', '.join(VALID_REASONING_EFFORTS)}. "
78+
"Ignoring environment variable.",
79+
UserWarning,
80+
stacklevel=2,
81+
)
82+
return None
83+
84+
return value_lower # type: ignore[return-value]
85+
86+
87+
def get_default_reasoning(
88+
effort: Optional[ReasoningEffort] = None,
89+
summary: Optional[Literal["auto", "concise", "detailed"]] = None,
90+
) -> Optional[Reasoning]:
91+
"""Get a Reasoning configuration, using environment variable as default.
92+
93+
This function allows you to easily configure reasoning with support for
94+
the OPENAI_REASONING_EFFORT environment variable.
95+
96+
Args:
97+
effort: Override the reasoning effort. If None, uses OPENAI_REASONING_EFFORT
98+
environment variable if set.
99+
summary: Optional summary configuration for reasoning output.
100+
101+
Returns:
102+
A Reasoning TypedDict if effort is configured (either explicitly or via
103+
environment variable), None otherwise.
104+
105+
Precedence:
106+
1. Explicit `effort` parameter (if provided)
107+
2. OPENAI_REASONING_EFFORT environment variable (if set)
108+
3. None (SDK default behavior)
109+
110+
Example:
111+
>>> # With environment variable set:
112+
>>> import os
113+
>>> os.environ["OPENAI_REASONING_EFFORT"] = "low"
114+
>>> get_default_reasoning()
115+
{'effort': 'low'}
116+
117+
>>> # With explicit override:
118+
>>> get_default_reasoning(effort="high")
119+
{'effort': 'high'}
120+
121+
>>> # With no configuration:
122+
>>> del os.environ["OPENAI_REASONING_EFFORT"]
123+
>>> get_default_reasoning()
124+
None
125+
"""
126+
# Determine the effort value
127+
final_effort: Optional[ReasoningEffort] = None
128+
129+
if effort is not None:
130+
final_effort = effort
131+
else:
132+
final_effort = get_reasoning_effort_from_env()
133+
134+
# If no effort is configured, return None
135+
if final_effort is None and summary is None:
136+
return None
137+
138+
# Build the Reasoning config
139+
result: Reasoning = {}
140+
if final_effort is not None:
141+
result["effort"] = final_effort
142+
if summary is not None:
143+
result["summary"] = summary
144+
145+
return result

tests/lib/test_reasoning.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""Tests for reasoning utilities.
2+
3+
Tests the OPENAI_REASONING_EFFORT environment variable support.
4+
Relates to issue #2686: Allow setting reasoning effort via environment variable
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import os
10+
import warnings
11+
12+
import pytest
13+
14+
from openai.lib import get_default_reasoning, get_reasoning_effort_from_env
15+
16+
17+
class TestGetReasoningEffortFromEnv:
18+
"""Tests for get_reasoning_effort_from_env function."""
19+
20+
def setup_method(self) -> None:
21+
"""Clean up env var before each test."""
22+
if "OPENAI_REASONING_EFFORT" in os.environ:
23+
del os.environ["OPENAI_REASONING_EFFORT"]
24+
25+
def teardown_method(self) -> None:
26+
"""Clean up env var after each test."""
27+
if "OPENAI_REASONING_EFFORT" in os.environ:
28+
del os.environ["OPENAI_REASONING_EFFORT"]
29+
30+
def test_returns_none_when_not_set(self) -> None:
31+
"""Returns None when env var is not set."""
32+
result = get_reasoning_effort_from_env()
33+
assert result is None
34+
35+
@pytest.mark.parametrize(
36+
"value",
37+
["none", "minimal", "low", "medium", "high", "xhigh"],
38+
)
39+
def test_returns_valid_values(self, value: str) -> None:
40+
"""Returns the value when it's valid."""
41+
os.environ["OPENAI_REASONING_EFFORT"] = value
42+
result = get_reasoning_effort_from_env()
43+
assert result == value
44+
45+
def test_case_insensitive(self) -> None:
46+
"""Accepts case-insensitive values."""
47+
os.environ["OPENAI_REASONING_EFFORT"] = "HIGH"
48+
result = get_reasoning_effort_from_env()
49+
assert result == "high"
50+
51+
def test_strips_whitespace(self) -> None:
52+
"""Strips leading/trailing whitespace."""
53+
os.environ["OPENAI_REASONING_EFFORT"] = " low "
54+
result = get_reasoning_effort_from_env()
55+
assert result == "low"
56+
57+
def test_warns_on_invalid_value(self) -> None:
58+
"""Warns and returns None for invalid values."""
59+
os.environ["OPENAI_REASONING_EFFORT"] = "invalid"
60+
with warnings.catch_warnings(record=True) as w:
61+
warnings.simplefilter("always")
62+
result = get_reasoning_effort_from_env()
63+
assert result is None
64+
assert len(w) == 1
65+
assert "Invalid" in str(w[0].message)
66+
assert "OPENAI_REASONING_EFFORT" in str(w[0].message)
67+
68+
69+
class TestGetDefaultReasoning:
70+
"""Tests for get_default_reasoning function."""
71+
72+
def setup_method(self) -> None:
73+
"""Clean up env var before each test."""
74+
if "OPENAI_REASONING_EFFORT" in os.environ:
75+
del os.environ["OPENAI_REASONING_EFFORT"]
76+
77+
def teardown_method(self) -> None:
78+
"""Clean up env var after each test."""
79+
if "OPENAI_REASONING_EFFORT" in os.environ:
80+
del os.environ["OPENAI_REASONING_EFFORT"]
81+
82+
def test_returns_none_when_no_config(self) -> None:
83+
"""Returns None when no effort configured."""
84+
result = get_default_reasoning()
85+
assert result is None
86+
87+
def test_uses_env_var(self) -> None:
88+
"""Uses env var when no explicit effort provided."""
89+
os.environ["OPENAI_REASONING_EFFORT"] = "low"
90+
result = get_default_reasoning()
91+
assert result == {"effort": "low"}
92+
93+
def test_explicit_effort_overrides_env(self) -> None:
94+
"""Explicit effort parameter overrides env var."""
95+
os.environ["OPENAI_REASONING_EFFORT"] = "low"
96+
result = get_default_reasoning(effort="high")
97+
assert result == {"effort": "high"}
98+
99+
def test_with_summary(self) -> None:
100+
"""Can configure summary alongside effort."""
101+
os.environ["OPENAI_REASONING_EFFORT"] = "medium"
102+
result = get_default_reasoning(summary="concise")
103+
assert result == {"effort": "medium", "summary": "concise"}
104+
105+
def test_summary_only(self) -> None:
106+
"""Can configure summary without effort."""
107+
result = get_default_reasoning(summary="detailed")
108+
assert result == {"summary": "detailed"}
109+
110+
def test_explicit_none_effort_uses_env(self) -> None:
111+
"""Explicit None for effort still uses env var."""
112+
os.environ["OPENAI_REASONING_EFFORT"] = "high"
113+
result = get_default_reasoning(effort=None)
114+
assert result == {"effort": "high"}

0 commit comments

Comments
 (0)