Files
agentic-mobile-control/runtime/planner_config.py
T
q792602257 a5aeb8889c
Tests / Test failed: 2, passed: 849
feat(runtime): add planner reflection history with rationale and thinking
- ToolCallDecision captures thinking blocks and pre-tool text output
- AnthropicToolCallingClient supports optional extended thinking (budget_tokens + beta header)
- PlannedStep carries rationale and thinking from each LLM decision
- WorldEvent replaces scene_summary with rationale/thinking/page fields (backward-compatible)
- AI planner system prompt instructs reflection before each tool call
- _history_summary() emits compact {page, rationale, action, success} dicts
- Cloud DB migration 0011 adds nullable rationale/thinking columns to planner_decision_log
- OpenAI client extracts reasoning_content into thinking field
2026-07-15 12:43:22 +08:00

77 lines
2.2 KiB
Python

from __future__ import annotations
import os
from collections.abc import Mapping
from dataclasses import dataclass
DEFAULT_PROVIDER = "anthropic"
DEFAULT_MODEL_BY_PROVIDER = {
"anthropic": "claude-sonnet-5",
"openai": "gpt-5.6",
}
DEFAULT_TIMEOUT_SECONDS = 30.0
ENABLED_ENV = "AI_PLANNER_ENABLED"
PROVIDER_ENV = "AI_PLANNER_PROVIDER"
MODEL_ENV = "AI_PLANNER_MODEL"
TIMEOUT_ENV = "AI_PLANNER_TIMEOUT_SECONDS"
THINKING_BUDGET_ENV = "AI_PLANNER_THINKING_BUDGET_TOKENS"
SUPPORTED_PROVIDERS = frozenset(DEFAULT_MODEL_BY_PROVIDER)
@dataclass(frozen=True)
class PlannerConfig:
enabled: bool = False
provider: str = DEFAULT_PROVIDER
model: str = ""
timeout: float = DEFAULT_TIMEOUT_SECONDS
thinking_budget_tokens: int | None = None
def resolved_model(self) -> str:
return self.model or DEFAULT_MODEL_BY_PROVIDER[self.provider]
def load_config(env: Mapping[str, str] | None = None) -> PlannerConfig:
values = env or os.environ
return PlannerConfig(
enabled=_parse_bool(values.get(ENABLED_ENV), default=False),
provider=_parse_provider(values.get(PROVIDER_ENV)),
model=values.get(MODEL_ENV) or "",
timeout=_parse_timeout(values.get(TIMEOUT_ENV)),
thinking_budget_tokens=_parse_thinking_budget(values.get(THINKING_BUDGET_ENV)),
)
def _parse_bool(value: str | None, *, default: bool) -> bool:
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on", "enabled"}
def _parse_provider(value: str | None) -> str:
if value is None:
return DEFAULT_PROVIDER
provider = value.strip().lower()
return provider if provider in SUPPORTED_PROVIDERS else DEFAULT_PROVIDER
def _parse_timeout(value: str | None) -> float:
if value is None:
return DEFAULT_TIMEOUT_SECONDS
try:
timeout = float(value)
except ValueError:
return DEFAULT_TIMEOUT_SECONDS
return timeout if timeout > 0 else DEFAULT_TIMEOUT_SECONDS
def _parse_thinking_budget(value: str | None) -> int | None:
if value is None:
return None
try:
budget = int(value)
except ValueError:
return None
return budget if budget > 0 else None