from __future__ import annotations from .shared_part_01 import * def _normalize_provider(raw: Optional[str]) -> str: provider = str(raw or "").strip().lower() provider = PROVIDER_ALIASES.get(provider, provider) if provider not in SUPPORTED_PROVIDERS: raise ValueError("provider 仅支持 deepseek/glm/openai/ollama") return provider def _mask_secret(secret: str) -> str: value = str(secret or "").strip() if not value: return "" if len(value) <= 8: return "*" * len(value) return f"{value[:4]}...{value[-4:]}" def _safe_json(value: Any) -> dict[str, Any]: if isinstance(value, dict): try: # 返回深拷贝,避免原地修改导致 SQLAlchemy 不感知 JSON 字段变更 return json.loads(json.dumps(value)) except Exception: return dict(value) if isinstance(value, str): try: parsed = json.loads(value) return parsed if isinstance(parsed, dict) else {} except Exception: return {} return {} def _optional_tier_alias(raw: Any) -> str: value = str(raw or "").strip().lower() return value if value in VALID_TIER_ALIASES else "" def _optional_response_contract(raw: Any) -> str: value = str(raw or "").strip().lower() return value if value in {"structured", "freeform"} else "" def _optional_fallback_policy(raw: Any) -> str: value = str(raw or "").strip().lower() return value if value in {"fast_local", "default", "disabled"} else "" def _optional_execution_mode(raw: Any) -> str: value = str(raw or "").strip().lower() return value if value in {"remote", "local_planner", "local_worker"} else "" def _normalize_routing_meta(raw: Any) -> dict[str, Any]: payload = _safe_json(raw) planned_tier = _optional_tier_alias(payload.get("planned_tier")) executed_tier = _optional_tier_alias(payload.get("executed_tier")) or planned_tier tier_alias = _optional_tier_alias(payload.get("tier_alias")) or planned_tier or executed_tier route_source = str(payload.get("route_source") or payload.get("source") or "").strip().lower() if route_source not in {"default", "override", "active_engine"}: route_source = "default" if tier_alias else "" rule_name = str(payload.get("route_rule_name") or payload.get("rule_name") or "").strip() decision = str(payload.get("routing_decision") or payload.get("decision") or "").strip() config_version = str(payload.get("routing_config_version") or "").strip() config_updated_at = str(payload.get("routing_config_updated_at") or "").strip() rule_priority_raw = payload.get("rule_priority") try: rule_priority = int(rule_priority_raw) if rule_priority_raw is not None else None except (TypeError, ValueError): rule_priority = None response_contract = _optional_response_contract(payload.get("response_contract")) fallback_policy = _optional_fallback_policy(payload.get("fallback_policy")) execution_mode = _optional_execution_mode(payload.get("execution_mode")) return { "tier_alias": tier_alias, "planned_tier": planned_tier or tier_alias, "executed_tier": executed_tier or planned_tier or tier_alias, "route_source": route_source, "route_rule_name": rule_name, "routing_decision": decision or "primary_tier", "routing_config_version": config_version, "routing_config_updated_at": config_updated_at, "rule_priority": rule_priority, "response_contract": response_contract, "fallback_policy": fallback_policy, "execution_mode": execution_mode, } def _overlay_engine_api_key_from_env(engine: dict[str, Any]) -> dict[str, Any]: runtime = dict(engine or {}) provider = str(runtime.get("provider") or runtime.get("source_provider") or "").strip().lower() if provider not in PROVIDER_DEFAULTS: return runtime defaults = dict(PROVIDER_DEFAULTS.get(provider) or {}) api_env = str(runtime.get("api_env") or defaults.get("api_env") or "").strip() if not api_env: return runtime env_api_key = str(os.getenv(api_env, "") or "").strip() if env_api_key: runtime["api_key"] = env_api_key runtime["api_env"] = api_env return runtime def _read_engine_routing_meta(engine: Any) -> dict[str, Any]: payload = engine.get("_routing_meta") if isinstance(engine, dict) else {} return _normalize_routing_meta(payload) def _merge_engine_routing_meta(engine: dict[str, Any], **updates: Any) -> dict[str, Any]: runtime = dict(engine or {}) current = _read_engine_routing_meta(runtime) for key, value in updates.items(): if value is None: continue current[key] = value runtime["_routing_meta"] = _normalize_routing_meta(current) return runtime def _routing_meta_log_text(meta: Any) -> str: data = _normalize_routing_meta(meta) parts = [] if data.get("planned_tier"): parts.append(f"planned_tier={data['planned_tier']}") if data.get("executed_tier"): parts.append(f"executed_tier={data['executed_tier']}") if data.get("route_source"): parts.append(f"source={data['route_source']}") if data.get("route_rule_name"): parts.append(f"rule={data['route_rule_name']}") if data.get("routing_decision"): parts.append(f"decision={data['routing_decision']}") return ", ".join(parts) def _env_bool(name: str, default: bool = False) -> bool: raw = os.getenv(name) if raw is None: return default return str(raw).strip().lower() in BOOL_TRUE_VALUES def _parse_csv_items(raw: str) -> tuple[str, ...]: rows = [item.strip().lower() for item in str(raw or "").split(",") if item.strip()] return tuple(rows) def _cn_now_text() -> str: cn_tz = timezone(timedelta(hours=8)) return datetime.now(cn_tz).strftime("%Y-%m-%d %H:%M:%S") def _cn_now_text_ms() -> str: cn_tz = timezone(timedelta(hours=8)) return datetime.now(cn_tz).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] def _runtime_log(scope: str, message: str) -> None: print(f"[{_cn_now_text()}][{scope}] {message}") def _is_openclaw_timeout_message(text: str) -> bool: lowered = str(text or "").strip().lower() if not lowered: return False timeout_signals = ( "request timed out before a response was generated", "agents.defaults.timeoutseconds", "timeoutseconds", "before a response was generated", ) return any(signal in lowered for signal in timeout_signals) @dataclass class ChatCompletionResult: text: str raw_response: Any = None @dataclass class StructuredExtractionResult: payload: Optional[dict[str, Any]] source_field: str provider: str model: str error_code: Optional[str] = None raw_text: str = "" validation: str = "invalid" from_cache: bool = False __all__ = [name for name in globals() if not name.startswith("__")]