mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
389 lines
12 KiB
Python
389 lines
12 KiB
Python
"""Effort level parsing, model capability checks, and API parameter mapping.
|
|
|
|
- string levels are ``low | medium | high | max``;
|
|
- ``auto`` / ``unset`` intentionally means "send no explicit effort";
|
|
- ``max`` is downgraded to ``high`` when the selected model does not support it;
|
|
- numeric effort is treated as an internal/session-local override only.
|
|
|
|
Provider-specific wire mapping is isolated in ``build_effort_request_params``
|
|
instead of leaking request fields through the rest of the engine.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
import os
|
|
from typing import Any, Literal
|
|
|
|
|
|
class EffortLevel(str, Enum):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
MAX = "max"
|
|
|
|
|
|
EFFORT_LEVELS: tuple[str, ...] = tuple(level.value for level in EffortLevel)
|
|
EffortValue = EffortLevel | int
|
|
EffortEnvState = Literal["absent", "auto", "value", "invalid"]
|
|
|
|
EFFORT_BUDGET_FRACTIONS: dict[EffortLevel, float] = {
|
|
EffortLevel.LOW: 0.15,
|
|
EffortLevel.MEDIUM: 0.35,
|
|
EffortLevel.HIGH: 0.70,
|
|
EffortLevel.MAX: 1.00,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class EffortConfig:
|
|
level: EffortLevel
|
|
api_effort: EffortLevel | None = None
|
|
thinking_budget_tokens: int | None = None
|
|
source: str = "default"
|
|
applied_value: EffortValue | None = None
|
|
|
|
|
|
def _is_truthy(value: str | None) -> bool:
|
|
return str(value or "").strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def _canonical_model(model: str) -> str:
|
|
return str(model or "").strip().lower().replace(".", "-")
|
|
|
|
|
|
def _model_tokens(model: str) -> set[str]:
|
|
lowered = _canonical_model(model)
|
|
tokens = {lowered}
|
|
if "/" in lowered:
|
|
tokens.add(lowered.rsplit("/", 1)[-1])
|
|
return tokens
|
|
|
|
|
|
def _csv_items(env_name: str) -> list[str]:
|
|
raw = os.environ.get(env_name, "")
|
|
return [item.strip().lower().replace(".", "-") for item in raw.split(",") if item.strip()]
|
|
|
|
|
|
def _env_matches(model: str, env_name: str) -> bool:
|
|
tokens = _model_tokens(model)
|
|
lowered = _canonical_model(model)
|
|
return any(item in tokens or item in lowered for item in _csv_items(env_name))
|
|
|
|
|
|
def _is_openai_reasoning_model(model: str) -> bool:
|
|
m = _canonical_model(model).rsplit("/", 1)[-1]
|
|
return (
|
|
m.startswith("o1")
|
|
or m.startswith("o3")
|
|
or m.startswith("o4")
|
|
or m.startswith("gpt-5")
|
|
)
|
|
|
|
|
|
def _is_anthropic_effort_model(model: str) -> bool:
|
|
m = _canonical_model(model)
|
|
return "opus-4-6" in m or "sonnet-4-6" in m
|
|
|
|
|
|
def _numeric_effort_enabled() -> bool:
|
|
return os.environ.get("USER_TYPE") == "ant" or _is_truthy(
|
|
os.environ.get("OPENSPACE_ALLOW_NUMERIC_EFFORT")
|
|
)
|
|
|
|
|
|
def is_effort_level(value: str) -> bool:
|
|
return str(value).strip().lower() in EFFORT_LEVELS
|
|
|
|
|
|
def is_valid_numeric_effort(value: int | float) -> bool:
|
|
return isinstance(value, int) and not isinstance(value, bool)
|
|
|
|
|
|
def parse_effort_value(value: object) -> EffortValue | None:
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, EffortLevel):
|
|
return value
|
|
if is_valid_numeric_effort(value): # type: ignore[arg-type]
|
|
return int(value) # type: ignore[arg-type]
|
|
|
|
text = str(value).strip().lower()
|
|
if text in {"", "auto", "unset", "none"}:
|
|
return None
|
|
if is_effort_level(text):
|
|
return EffortLevel(text)
|
|
try:
|
|
numeric = int(text, 10)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
return numeric if is_valid_numeric_effort(numeric) else None
|
|
|
|
|
|
def to_persistable_effort(value: EffortValue | str | None) -> EffortLevel | None:
|
|
parsed = parse_effort_value(value)
|
|
if parsed in {EffortLevel.LOW, EffortLevel.MEDIUM, EffortLevel.HIGH}:
|
|
return parsed # type: ignore[return-value]
|
|
if parsed == EffortLevel.MAX and _numeric_effort_enabled():
|
|
return EffortLevel.MAX
|
|
return None
|
|
|
|
|
|
def resolve_picker_effort_persistence(
|
|
picked: EffortLevel | str | None,
|
|
model_default: EffortLevel | str,
|
|
prior_persisted: EffortLevel | str | None,
|
|
toggled_in_picker: bool,
|
|
) -> EffortLevel | None:
|
|
picked_level = parse_effort_value(picked)
|
|
default_level = convert_effort_value_to_level(parse_effort_value(model_default) or EffortLevel.HIGH)
|
|
prior_level = to_persistable_effort(prior_persisted)
|
|
had_explicit = prior_level is not None or toggled_in_picker
|
|
if picked_level is None:
|
|
return None
|
|
picked_display = convert_effort_value_to_level(picked_level)
|
|
if had_explicit or picked_display != default_level:
|
|
return to_persistable_effort(picked_display)
|
|
return None
|
|
|
|
|
|
def _raw_effort_env() -> str | None:
|
|
return os.environ.get("OPENSPACE_EFFORT_LEVEL")
|
|
|
|
|
|
def get_effort_env_override() -> EffortValue | None:
|
|
state, value = _get_effort_env_state()
|
|
return value if state == "value" else None
|
|
|
|
|
|
def get_effort_env_state() -> tuple[EffortEnvState, EffortValue | None, str | None]:
|
|
state, value = _get_effort_env_state()
|
|
return state, value, _raw_effort_env()
|
|
|
|
|
|
def _get_effort_env_state() -> tuple[EffortEnvState, EffortValue | None]:
|
|
raw = _raw_effort_env()
|
|
if raw is None:
|
|
return "absent", None
|
|
normalized = raw.strip().lower()
|
|
if normalized in {"unset", "auto"}:
|
|
return "auto", None
|
|
parsed = parse_effort_value(normalized)
|
|
if parsed is None:
|
|
return "invalid", None
|
|
return "value", parsed
|
|
|
|
|
|
def model_supports_effort(model: str) -> bool:
|
|
if _is_truthy(os.environ.get("OPENSPACE_ALWAYS_ENABLE_EFFORT")):
|
|
return True
|
|
if _env_matches(model, "OPENSPACE_NO_EFFORT_MODELS"):
|
|
return False
|
|
if _env_matches(model, "OPENSPACE_EFFORT_MODELS"):
|
|
return True
|
|
if _is_anthropic_effort_model(model):
|
|
return True
|
|
if _is_openai_reasoning_model(model):
|
|
return True
|
|
return False
|
|
|
|
|
|
def model_supports_max_effort(model: str) -> bool:
|
|
if _env_matches(model, "OPENSPACE_MAX_EFFORT_MODELS"):
|
|
return True
|
|
if _env_matches(model, "OPENSPACE_NO_MAX_EFFORT_MODELS"):
|
|
return False
|
|
return "opus-4-6" in _canonical_model(model)
|
|
|
|
|
|
def get_default_effort_for_model(model: str) -> EffortLevel | None:
|
|
env_default = os.environ.get("OPENSPACE_DEFAULT_EFFORT_LEVEL")
|
|
if env_default is not None:
|
|
if env_default.strip().lower() in {"", "auto", "unset", "none"}:
|
|
return None
|
|
parsed = parse_effort_value(env_default)
|
|
if parsed is not None:
|
|
level = convert_effort_value_to_level(parsed)
|
|
if level == EffortLevel.MAX and not model_supports_max_effort(model):
|
|
return EffortLevel.HIGH
|
|
return level
|
|
|
|
# Keep a conservative product recommendation for Opus 4.6 and avoid adding
|
|
# defaults for other effort-capable models.
|
|
if "opus-4-6" in _canonical_model(model):
|
|
return EffortLevel.MEDIUM
|
|
return None
|
|
|
|
|
|
def resolve_applied_effort(
|
|
model: str,
|
|
requested: str | int | EffortLevel | None,
|
|
) -> EffortValue | None:
|
|
env_state, env_value = _get_effort_env_state()
|
|
if env_state == "auto":
|
|
return None
|
|
if env_state == "value":
|
|
resolved: EffortValue | None = env_value
|
|
else:
|
|
resolved = parse_effort_value(requested) or get_default_effort_for_model(model)
|
|
|
|
if resolved is None:
|
|
return None
|
|
if isinstance(resolved, int) and not _numeric_effort_enabled():
|
|
return EffortLevel.HIGH
|
|
if resolved == EffortLevel.MAX and not model_supports_max_effort(model):
|
|
return EffortLevel.HIGH
|
|
return resolved
|
|
|
|
|
|
def convert_effort_value_to_level(value: EffortValue | str) -> EffortLevel:
|
|
parsed = parse_effort_value(value)
|
|
if isinstance(parsed, EffortLevel):
|
|
return parsed
|
|
if isinstance(parsed, int) and _numeric_effort_enabled():
|
|
if parsed <= 50:
|
|
return EffortLevel.LOW
|
|
if parsed <= 85:
|
|
return EffortLevel.MEDIUM
|
|
if parsed <= 100:
|
|
return EffortLevel.HIGH
|
|
return EffortLevel.MAX
|
|
return EffortLevel.HIGH
|
|
|
|
|
|
def get_displayed_effort_level(
|
|
model: str,
|
|
requested: str | int | EffortLevel | None,
|
|
) -> EffortLevel:
|
|
return convert_effort_value_to_level(
|
|
resolve_applied_effort(model, requested) or EffortLevel.HIGH
|
|
)
|
|
|
|
|
|
def get_effort_suffix(
|
|
model: str,
|
|
effort_value: str | int | EffortLevel | None,
|
|
) -> str:
|
|
if parse_effort_value(effort_value) is None:
|
|
return ""
|
|
resolved = resolve_applied_effort(model, effort_value)
|
|
if resolved is None:
|
|
return ""
|
|
return f" with {convert_effort_value_to_level(resolved).value} effort"
|
|
|
|
|
|
def get_effort_level_description(level: EffortLevel | str) -> str:
|
|
parsed = convert_effort_value_to_level(level)
|
|
if parsed == EffortLevel.LOW:
|
|
return "Quick, straightforward implementation with minimal overhead"
|
|
if parsed == EffortLevel.MEDIUM:
|
|
return "Balanced approach with standard implementation and testing"
|
|
if parsed == EffortLevel.HIGH:
|
|
return "Comprehensive implementation with extensive testing and documentation"
|
|
return "Maximum capability with deepest reasoning (Opus 4.6 only)"
|
|
|
|
|
|
def get_effort_value_description(value: EffortValue | str) -> str:
|
|
parsed = parse_effort_value(value)
|
|
if isinstance(parsed, int) and _numeric_effort_enabled():
|
|
return f"[INTERNAL] Numeric effort value of {parsed}"
|
|
if parsed is not None:
|
|
return get_effort_level_description(convert_effort_value_to_level(parsed))
|
|
return get_effort_level_description(EffortLevel.MEDIUM)
|
|
|
|
|
|
def _round_down_to_multiple(value: int, multiple: int = 256) -> int:
|
|
if value <= 0:
|
|
return 0
|
|
return max(multiple, (value // multiple) * multiple)
|
|
|
|
|
|
def effort_to_thinking_budget(level: str | int | EffortLevel | None, model: str) -> int:
|
|
parsed = parse_effort_value(level)
|
|
if isinstance(parsed, int) and parsed > 0 and _numeric_effort_enabled():
|
|
return int(parsed)
|
|
effort_level = convert_effort_value_to_level(parsed or EffortLevel.MEDIUM)
|
|
fraction = EFFORT_BUDGET_FRACTIONS[effort_level]
|
|
from .thinking import get_max_thinking_tokens_for_model
|
|
|
|
return _round_down_to_multiple(
|
|
int(get_max_thinking_tokens_for_model(model) * fraction)
|
|
)
|
|
|
|
|
|
def get_effort_config(
|
|
model: str,
|
|
level: str | int | EffortLevel | None,
|
|
) -> EffortConfig:
|
|
applied = resolve_applied_effort(model, level)
|
|
if applied is None:
|
|
return EffortConfig(
|
|
level=get_displayed_effort_level(model, level),
|
|
source="auto",
|
|
applied_value=None,
|
|
)
|
|
|
|
display_level = convert_effort_value_to_level(applied)
|
|
source = "env" if _get_effort_env_state()[0] == "value" else "explicit"
|
|
if parse_effort_value(level) is None and _get_effort_env_state()[0] != "value":
|
|
source = "model_default"
|
|
|
|
if model_supports_effort(model):
|
|
return EffortConfig(
|
|
level=display_level,
|
|
api_effort=display_level,
|
|
source=source,
|
|
applied_value=applied,
|
|
)
|
|
|
|
from .thinking import supports_thinking
|
|
|
|
if supports_thinking(model):
|
|
return EffortConfig(
|
|
level=display_level,
|
|
thinking_budget_tokens=effort_to_thinking_budget(applied, model),
|
|
source=source,
|
|
applied_value=applied,
|
|
)
|
|
|
|
return EffortConfig(level=display_level, source=source, applied_value=applied)
|
|
|
|
|
|
def build_effort_request_params(config: EffortConfig, model: str) -> dict[str, Any]:
|
|
if config.api_effort is None:
|
|
return {}
|
|
effort = config.api_effort.value
|
|
if _is_openai_reasoning_model(model):
|
|
return {"reasoning_effort": effort}
|
|
if _is_anthropic_effort_model(model):
|
|
return {"extra_body": {"output_config": {"effort": effort}}}
|
|
return {"reasoning_effort": effort}
|
|
|
|
|
|
__all__ = [
|
|
"EFFORT_BUDGET_FRACTIONS",
|
|
"EFFORT_LEVELS",
|
|
"EffortConfig",
|
|
"EffortLevel",
|
|
"EffortValue",
|
|
"build_effort_request_params",
|
|
"convert_effort_value_to_level",
|
|
"effort_to_thinking_budget",
|
|
"get_default_effort_for_model",
|
|
"get_displayed_effort_level",
|
|
"get_effort_env_override",
|
|
"get_effort_env_state",
|
|
"get_effort_level_description",
|
|
"get_effort_suffix",
|
|
"get_effort_value_description",
|
|
"get_effort_config",
|
|
"is_effort_level",
|
|
"is_valid_numeric_effort",
|
|
"model_supports_effort",
|
|
"model_supports_max_effort",
|
|
"parse_effort_value",
|
|
"resolve_applied_effort",
|
|
"resolve_picker_effort_persistence",
|
|
"to_persistable_effort",
|
|
]
|