OpenSpace/openspace/llm/effort.py
2026-07-17 11:43:42 +08:00

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",
]