PMFlow/backend/app/services/llm_service/shared_part_02.py

86 lines
2.6 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 _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 _looks_like_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("__")]