| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- """通用工具函数 —— 只包含与业务无关的基础设施工具"""
- import hashlib
- import json
- import random
- import re
- import string
- from datetime import datetime, timezone, date, timedelta
- from typing import Dict, List, Optional
- def safe_json_parse(text: str) -> Optional[Dict | List]:
- """多层降级解析 JSON:直接解析 → 提取代码块 → 提取 JSON 对象/数组
- LLM 可能返回 ```json ... ``` 包裹的文本,或文本中夹杂 markdown 前缀/后缀。
- 先尝试直接解析(最常见路径),失败后逐层降级提取。
- """
- if not text:
- return None
- # 降级 1:直接解析
- try:
- return json.loads(text)
- except (json.JSONDecodeError, TypeError):
- pass
- clean = text.strip()
- # 降级 2:提取最外层 json 代码块
- m = re.search(r"```json\s*(.*?)\s*```", clean, re.DOTALL)
- if m:
- try:
- return json.loads(m.group(1))
- except (json.JSONDecodeError, TypeError):
- pass
- else:
- m = re.search(r"```\s*(.*?)\s*```", clean, re.DOTALL)
- if m:
- try:
- return json.loads(m.group(1))
- except (json.JSONDecodeError, TypeError):
- pass
- # 降级 3:在文本中查找第一个完整 JSON 对象 { ... } 或数组 [ ... ]
- for bracket_pair in [("{}", "{", "}"), ("[]", "[", "]")]:
- opener, closer = bracket_pair[1], bracket_pair[2]
- start = clean.find(opener)
- if start == -1:
- continue
- depth = 0
- in_string = False
- escape_next = False
- for i in range(start, len(clean)):
- ch = clean[i]
- if escape_next:
- escape_next = False
- continue
- if ch == "\\":
- escape_next = True
- continue
- if ch == '"' and not escape_next:
- in_string = not in_string
- continue
- if in_string:
- continue
- if ch == opener:
- depth += 1
- elif ch == closer:
- depth -= 1
- if depth == 0:
- try:
- return json.loads(clean[start : i + 1])
- except (json.JSONDecodeError, TypeError):
- return None
- try:
- return json.loads(clean[start:])
- except (json.JSONDecodeError, TypeError):
- pass
- return None
- def str_to_md5(strings: str) -> str:
- """字符串转 MD5"""
- original_bytes = strings.encode("utf-8")
- md5_hash = hashlib.md5()
- md5_hash.update(original_bytes)
- return md5_hash.hexdigest()
- def generate_task_trace_id() -> str:
- """生成任务追踪 ID,格式: Task-YYYYMMDDHHmmss-16位随机"""
- random_str = "".join(random.choices(string.ascii_lowercase + string.digits, k=16))
- return f"Task-{datetime.now().strftime('%Y%m%d%H%M%S')}-{random_str}"
- def yield_batch(data: list, batch_size: int):
- """生成批次数据"""
- for i in range(0, len(data), batch_size):
- yield data[i : i + batch_size]
- def timestamp_to_str(timestamp: int, string_format: str = "%Y-%m-%d %H:%M:%S") -> str:
- """时间戳转字符串"""
- dt_object = (
- datetime.utcfromtimestamp(timestamp).replace(tzinfo=timezone.utc).astimezone()
- )
- return dt_object.strftime(string_format)
- def days_remaining_in_month() -> int:
- """计算本月剩余天数"""
- today = date.today()
- if today.month == 12:
- next_month = today.replace(year=today.year + 1, month=1, day=1)
- else:
- next_month = today.replace(month=today.month + 1, day=1)
- last_day_of_month = next_month - timedelta(days=1)
- return (last_day_of_month - today).days
|