190 lines
6.9 KiB
Python
190 lines
6.9 KiB
Python
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_control_plane_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("__")]
|