""" telemetry.py - StoryWeaver 结构化交互日志工具 职责: 1. 为每个游戏会话分配稳定的 session_id 2. 以 JSONL 形式落盘每回合交互记录 3. 为评估脚本和案例分析提供统一的日志格式 """ from __future__ import annotations import json import os import uuid from datetime import datetime from pathlib import Path from typing import Any PROJECT_ROOT = Path(__file__).resolve().parent DEFAULT_LOG_DIR = PROJECT_ROOT / "logs" / "interactions" def _resolve_log_dir() -> Path: custom_dir = os.getenv("STORYWEAVER_LOG_DIR", "").strip() if custom_dir: return Path(custom_dir).expanduser() return DEFAULT_LOG_DIR def create_session_metadata(session_id: str | None = None) -> dict[str, Any]: """ 创建新的会话元数据。 每个会话对应一个单独的 JSONL 文件,便于回放和分析。 """ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") new_session_id = session_id or f"sw-{timestamp}-{uuid.uuid4().hex[:8]}" log_dir = _resolve_log_dir() log_path = log_dir / f"{new_session_id}.jsonl" return { "session_id": new_session_id, "turn_index": 0, "interaction_log_path": str(log_path), } def ensure_session_metadata(game_session: dict[str, Any]) -> dict[str, Any]: """确保游戏会话中带有日志所需的元数据。""" if "session_id" not in game_session or "interaction_log_path" not in game_session: game_session.update(create_session_metadata()) if "turn_index" not in game_session: game_session["turn_index"] = 0 return game_session def append_turn_log(game_session: dict[str, Any], record: dict[str, Any]) -> str: """ 追加一条结构化交互日志。 Returns: 日志文件路径,便于调试和脚本复用。 """ ensure_session_metadata(game_session) game_session["turn_index"] += 1 log_path = Path(game_session["interaction_log_path"]) log_path.parent.mkdir(parents=True, exist_ok=True) payload = { "timestamp": datetime.now().isoformat(timespec="seconds"), "session_id": game_session["session_id"], "turn_index": game_session["turn_index"], **record, } with log_path.open("a", encoding="utf-8") as fh: json.dump(payload, fh, ensure_ascii=False) fh.write("\n") return str(log_path)