File size: 2,390 Bytes
1a91c20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
"""
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)