93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
"""AI PMO kernel closure helpers mixin."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from . import ai_pmo_kernel_common as _common
|
|
|
|
for _name, _value in vars(_common).items():
|
|
if not _name.startswith("__"):
|
|
globals()[_name] = _value
|
|
|
|
|
|
class KernelClosureHelpersMixin:
|
|
@staticmethod
|
|
def _normalize_dialogue_text(value: Any, *, max_len: int) -> str:
|
|
text = re.sub(r"\s+", " ", str(value or "")).strip()
|
|
if not text:
|
|
return ""
|
|
return text[:max_len]
|
|
|
|
@staticmethod
|
|
def _parse_datetime_like(value: Any) -> Optional[datetime]:
|
|
if isinstance(value, datetime):
|
|
dt = value
|
|
else:
|
|
raw = str(value or "").strip()
|
|
if not raw:
|
|
return None
|
|
try:
|
|
dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
|
except Exception:
|
|
return None
|
|
if dt.tzinfo is not None:
|
|
return dt.astimezone(timezone.utc).replace(tzinfo=None)
|
|
return dt
|
|
|
|
@staticmethod
|
|
def _normalize_feishu_user_text(value: Any, *, max_len: int) -> str:
|
|
text = str(value or "")
|
|
text = re.sub(r"^@_user_\d+\s*", "", text)
|
|
text = re.sub(r"</?at[^>]*>", " ", text, flags=re.IGNORECASE)
|
|
text = re.sub(r"\s+", " ", text).strip()
|
|
if not text:
|
|
return ""
|
|
return text[:max_len]
|
|
|
|
@classmethod
|
|
def _extract_assistant_text_from_log_payload(cls, payload: Any) -> str:
|
|
if not isinstance(payload, dict):
|
|
return ""
|
|
candidates = (
|
|
payload.get("followup_reply"),
|
|
payload.get("direct_reply_text"),
|
|
payload.get("clarification_message"),
|
|
payload.get("answer"),
|
|
payload.get("reason"),
|
|
payload.get("result_summary"),
|
|
payload.get("error_message"),
|
|
payload.get("message"),
|
|
)
|
|
for item in candidates:
|
|
text = cls._normalize_dialogue_text(item, max_len=420)
|
|
if not text:
|
|
continue
|
|
if any(token in text for token in ("chat_id", "message_type", "sender_name")) and len(text) < 48:
|
|
continue
|
|
return text
|
|
return ""
|
|
|
|
@staticmethod
|
|
def _merge_closure_turns(
|
|
base_turns: list[dict[str, str]],
|
|
extra_turns: list[dict[str, str]],
|
|
*,
|
|
prepend: bool = False,
|
|
) -> list[dict[str, str]]:
|
|
if not extra_turns:
|
|
return list(base_turns)
|
|
merged = list(extra_turns) + list(base_turns) if prepend else list(base_turns) + list(extra_turns)
|
|
compact: list[dict[str, str]] = []
|
|
for row in merged:
|
|
role = str(row.get("role") or "").strip().lower()
|
|
text = str(row.get("text") or "").strip()
|
|
if role not in {"user", "assistant"} or not text:
|
|
continue
|
|
if compact and compact[-1].get("role") == role and compact[-1].get("text") == text:
|
|
continue
|
|
compact.append({"role": role, "text": text})
|
|
return compact[-8:]
|
|
|
|
@staticmethod
|
|
def _turns_have_assistant(turns: list[dict[str, str]]) -> bool:
|
|
return any(str(turn.get("role") or "").strip().lower() == "assistant" and str(turn.get("text") or "").strip() for turn in turns)
|