File size: 8,108 Bytes
a5784e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
Thinking Mode Parameter Normalization Module
Normalizes the reasoning_effort parameter into a standardized thinking directive.

This module is responsible for converting various formats of the reasoning_effort parameter into a unified internal directive structure.
"""

from dataclasses import dataclass
from typing import Any, Optional

from config import DEFAULT_THINKING_BUDGET, ENABLE_THINKING_BUDGET
from config.settings import (
    DISABLE_THINKING_BUDGET_ON_STREAMING_DISABLE,
    THINKING_BUDGET_HIGH,
    THINKING_BUDGET_LOW,
    THINKING_BUDGET_MEDIUM,
)


@dataclass
class ThinkingDirective:
    """Standardized thinking directive

    Attributes:
        thinking_enabled: Whether thinking mode is enabled (master switch)
        budget_enabled: Whether to limit thinking budget
        budget_value: Budget token count (valid only when budget_enabled=True)
        original_value: Original reasoning_effort value (for logging)
    """

    thinking_enabled: bool
    budget_enabled: bool
    budget_value: Optional[int]
    original_value: Any


def normalize_reasoning_effort(
    reasoning_effort: Optional[Any], is_streaming: bool = True
) -> ThinkingDirective:
    """Normalize reasoning_effort parameter into a standardized thinking directive

    Args:
        reasoning_effort: reasoning_effort parameter in API request, possible values:
            - None: Use default configuration
            - 0 or "0": Disable thinking mode
            - Positive integer: Enable thinking, set specific budget value
            - "low"/"medium"/"high": Enable thinking, use preset budget
            - "none" or "-1" or -1: Enable thinking, unlimited budget
        is_streaming: Whether it is a streaming request (affects DISABLE_THINKING_BUDGET_ON_STREAMING_DISABLE config)

    Returns:
        ThinkingDirective: Standardized thinking directive

    Example:
        >>> normalize_reasoning_effort(None)
        ThinkingDirective(thinking_enabled=False, budget_enabled=False, budget_value=None, ...)

        >>> normalize_reasoning_effort(0)
        ThinkingDirective(thinking_enabled=False, budget_enabled=False, budget_value=None, ...)

        >>> normalize_reasoning_effort("medium")
        ThinkingDirective(thinking_enabled=True, budget_enabled=True, budget_value=8000, ...)

        >>> normalize_reasoning_effort("none")
        ThinkingDirective(thinking_enabled=True, budget_enabled=False, budget_value=None, ...)
    """

    # Scenario 1: User unspecified, use default configuration
    if reasoning_effort is None:
        return ThinkingDirective(
            thinking_enabled=ENABLE_THINKING_BUDGET,
            budget_enabled=ENABLE_THINKING_BUDGET,
            budget_value=DEFAULT_THINKING_BUDGET if ENABLE_THINKING_BUDGET else None,
            original_value=None,
        )

    # Scenario 2: Disable thinking mode (reasoning_effort = 0 or "0")
    if reasoning_effort == 0 or (
        isinstance(reasoning_effort, str) and reasoning_effort.strip() == "0"
    ):
        return ThinkingDirective(
            thinking_enabled=False,
            budget_enabled=False,
            budget_value=None,
            original_value=reasoning_effort,
        )

    # Scenario 3: Enable thinking but unlimited budget (reasoning_effort = "none" / "-1" / -1)
    if isinstance(reasoning_effort, str):
        reasoning_str = reasoning_effort.strip().lower()
        # "none"/"-1" → enable thinking, unlimited budget
        if reasoning_str in ["none", "-1"]:
            return ThinkingDirective(
                thinking_enabled=True,
                budget_enabled=False,
                budget_value=None,
                original_value=reasoning_effort,
            )
        # "high"/"low"/"medium" → enable thinking, use _should_enable_from_raw logic
        # Note: these values are handled by _should_enable_from_raw in _handle_thinking_budget
        # Returning thinking_enabled=True here to avoid conflict with desired_enabled
        if reasoning_str in ["high", "low", "medium"]:
            return ThinkingDirective(
                thinking_enabled=True,
                budget_enabled=False,  # Actual value determined by _should_enable_from_raw
                budget_value=None,
                original_value=reasoning_effort,
            )
    elif reasoning_effort == -1:
        return ThinkingDirective(
            thinking_enabled=True,
            budget_enabled=False,
            budget_value=None,
            original_value=reasoning_effort,
        )

    # Scenario 4: Enable thinking and limit budget (specific number or preset value)
    budget_value = _parse_budget_value(reasoning_effort)

    if budget_value is not None and budget_value > 0:
        return ThinkingDirective(
            thinking_enabled=True,
            budget_enabled=True,
            budget_value=budget_value,
            original_value=reasoning_effort,
        )

    # Invalid value: Use default configuration
    return ThinkingDirective(
        thinking_enabled=ENABLE_THINKING_BUDGET,
        budget_enabled=ENABLE_THINKING_BUDGET,
        budget_value=DEFAULT_THINKING_BUDGET if ENABLE_THINKING_BUDGET else None,
        original_value=reasoning_effort,
    )


def normalize_reasoning_effort_with_stream_check(
    reasoning_effort: Optional[Any], is_streaming: bool = True
) -> ThinkingDirective:
    """Normalize thinking directive with stream check

    Decides whether to disable thinking budget in non-streaming mode based on DISABLE_THINKING_BUDGET_ON_STREAMING_DISABLE configuration.

    Args:
        reasoning_effort: reasoning_effort parameter from API request
        is_streaming: Whether it is a streaming request

    Returns:
        ThinkingDirective: Standardized thinking directive
    """
    # First get the basic thinking directive
    directive = normalize_reasoning_effort(reasoning_effort, is_streaming)

    # If not streaming and configured to disable budget on streaming disable, then disable
    if not is_streaming and DISABLE_THINKING_BUDGET_ON_STREAMING_DISABLE:
        return ThinkingDirective(
            thinking_enabled=False,
            budget_enabled=False,
            budget_value=None,
            original_value=reasoning_effort,
        )

    # Otherwise return original directive (allowing thinking budget to remain enabled in non-streaming mode)
    return directive


def _parse_budget_value(reasoning_effort: Any) -> Optional[int]:
    """Parse budget value

    Args:
        reasoning_effort: reasoning_effort parameter value

    Returns:
        int: Budget token count, or None if parsing fails
    """
    # If integer, return directly
    if isinstance(reasoning_effort, int) and reasoning_effort > 0:
        return reasoning_effort

    # If string, try to parse as number
    if isinstance(reasoning_effort, str):
        effort_str = reasoning_effort.strip().lower()

        # Preset value mapping - use values from environment configuration
        effort_map = {
            "low": THINKING_BUDGET_LOW,
            "medium": THINKING_BUDGET_MEDIUM,
            "high": THINKING_BUDGET_HIGH,
        }

        # Try preset values first
        if effort_str in effort_map:
            return effort_map[effort_str]

        # Then try parsing as number
        try:
            value = int(effort_str)
            if value > 0:
                return value
        except (ValueError, TypeError):
            pass

    return None


def format_directive_log(directive: ThinkingDirective) -> str:
    """Format thinking directive as log string

    Args:
        directive: Thinking directive

    Returns:
        str: Formatted log string
    """
    if not directive.thinking_enabled:
        return f"Thinking mode disabled (Original: {directive.original_value})"
    elif directive.budget_enabled and directive.budget_value is not None:
        return f"Thinking enabled with budget: {directive.budget_value} tokens (Original: {directive.original_value})"
    else:
        return (
            f"Thinking enabled, unlimited budget (Original: {directive.original_value})"
        )