NewProject / telemetry.py
PPP
feat: add reproducible evaluation pipeline and structured interaction logging
1a91c20
"""
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)