docs(i18n): translate chinese docstrings/comments in backend/scripts/{test_profile_format,action_logger}

This commit is contained in:
Dominik Seemann 2026-05-07 14:51:05 +00:00
parent e1019d91cb
commit c8c455ceb4
2 changed files with 105 additions and 104 deletions

View File

@ -1,15 +1,17 @@
""" """Action logger.
动作日志记录器
用于记录OASIS模拟中每个Agent的动作供后端监控使用 Records each agent action during an OASIS simulation so the backend can
monitor progress.
Log layout::
日志结构:
sim_xxx/ sim_xxx/
twitter/ twitter/
actions.jsonl # Twitter 平台动作日志 actions.jsonl # Twitter action log
reddit/ reddit/
actions.jsonl # Reddit 平台动作日志 actions.jsonl # Reddit action log
simulation.log # 主模拟进程日志 simulation.log # main simulation process log
run_state.json # 运行状态API 查询用) run_state.json # run state (queried by the API)
""" """
import json import json
@ -20,15 +22,14 @@ from typing import Dict, Any, Optional
class PlatformActionLogger: class PlatformActionLogger:
"""单平台动作日志记录器""" """Per-platform action logger."""
def __init__(self, platform: str, base_dir: str): def __init__(self, platform: str, base_dir: str):
""" """Initialize the logger.
初始化日志记录器
Args: Args:
platform: 平台名称 (twitter/reddit) platform: Platform name (``twitter`` or ``reddit``).
base_dir: 模拟目录的基础路径 base_dir: Base path of the simulation directory.
""" """
self.platform = platform self.platform = platform
self.base_dir = base_dir self.base_dir = base_dir
@ -37,7 +38,7 @@ class PlatformActionLogger:
self._ensure_dir() self._ensure_dir()
def _ensure_dir(self): def _ensure_dir(self):
"""确保目录存在""" """Ensure the log directory exists."""
os.makedirs(self.log_dir, exist_ok=True) os.makedirs(self.log_dir, exist_ok=True)
def log_action( def log_action(
@ -50,7 +51,7 @@ class PlatformActionLogger:
result: Optional[str] = None, result: Optional[str] = None,
success: bool = True success: bool = True
): ):
"""记录一个动作""" """Append a single action record."""
entry = { entry = {
"round": round_num, "round": round_num,
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
@ -66,7 +67,7 @@ class PlatformActionLogger:
f.write(json.dumps(entry, ensure_ascii=False) + '\n') f.write(json.dumps(entry, ensure_ascii=False) + '\n')
def log_round_start(self, round_num: int, simulated_hour: int): def log_round_start(self, round_num: int, simulated_hour: int):
"""记录轮次开始""" """Append a round-start marker."""
entry = { entry = {
"round": round_num, "round": round_num,
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
@ -78,7 +79,7 @@ class PlatformActionLogger:
f.write(json.dumps(entry, ensure_ascii=False) + '\n') f.write(json.dumps(entry, ensure_ascii=False) + '\n')
def log_round_end(self, round_num: int, actions_count: int): def log_round_end(self, round_num: int, actions_count: int):
"""记录轮次结束""" """Append a round-end marker."""
entry = { entry = {
"round": round_num, "round": round_num,
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
@ -90,7 +91,7 @@ class PlatformActionLogger:
f.write(json.dumps(entry, ensure_ascii=False) + '\n') f.write(json.dumps(entry, ensure_ascii=False) + '\n')
def log_simulation_start(self, config: Dict[str, Any]): def log_simulation_start(self, config: Dict[str, Any]):
"""记录模拟开始""" """Append a simulation-start marker."""
entry = { entry = {
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
"event_type": "simulation_start", "event_type": "simulation_start",
@ -103,7 +104,7 @@ class PlatformActionLogger:
f.write(json.dumps(entry, ensure_ascii=False) + '\n') f.write(json.dumps(entry, ensure_ascii=False) + '\n')
def log_simulation_end(self, total_rounds: int, total_actions: int): def log_simulation_end(self, total_rounds: int, total_actions: int):
"""记录模拟结束""" """Append a simulation-end marker."""
entry = { entry = {
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
"event_type": "simulation_end", "event_type": "simulation_end",
@ -117,36 +118,36 @@ class PlatformActionLogger:
class SimulationLogManager: class SimulationLogManager:
""" """Top-level log manager.
模拟日志管理器
统一管理所有日志文件按平台分离 Owns and dispatches to the per-platform action loggers, and exposes a
main process logger for non-action messages.
""" """
def __init__(self, simulation_dir: str): def __init__(self, simulation_dir: str):
""" """Initialize the log manager.
初始化日志管理器
Args: Args:
simulation_dir: 模拟目录路径 simulation_dir: Path to the simulation directory.
""" """
self.simulation_dir = simulation_dir self.simulation_dir = simulation_dir
self.twitter_logger: Optional[PlatformActionLogger] = None self.twitter_logger: Optional[PlatformActionLogger] = None
self.reddit_logger: Optional[PlatformActionLogger] = None self.reddit_logger: Optional[PlatformActionLogger] = None
self._main_logger: Optional[logging.Logger] = None self._main_logger: Optional[logging.Logger] = None
# 设置主日志 # Configure the main process logger.
self._setup_main_logger() self._setup_main_logger()
def _setup_main_logger(self): def _setup_main_logger(self):
"""设置主模拟日志""" """Configure the main simulation log."""
log_path = os.path.join(self.simulation_dir, "simulation.log") log_path = os.path.join(self.simulation_dir, "simulation.log")
# 创建 logger # Build the logger.
self._main_logger = logging.getLogger(f"simulation.{os.path.basename(self.simulation_dir)}") self._main_logger = logging.getLogger(f"simulation.{os.path.basename(self.simulation_dir)}")
self._main_logger.setLevel(logging.INFO) self._main_logger.setLevel(logging.INFO)
self._main_logger.handlers.clear() self._main_logger.handlers.clear()
# 文件处理器 # File handler.
file_handler = logging.FileHandler(log_path, encoding='utf-8', mode='w') file_handler = logging.FileHandler(log_path, encoding='utf-8', mode='w')
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter( file_handler.setFormatter(logging.Formatter(
@ -155,7 +156,7 @@ class SimulationLogManager:
)) ))
self._main_logger.addHandler(file_handler) self._main_logger.addHandler(file_handler)
# 控制台处理器 # Console handler.
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter( console_handler.setFormatter(logging.Formatter(
@ -167,19 +168,19 @@ class SimulationLogManager:
self._main_logger.propagate = False self._main_logger.propagate = False
def get_twitter_logger(self) -> PlatformActionLogger: def get_twitter_logger(self) -> PlatformActionLogger:
"""获取 Twitter 平台日志记录器""" """Lazily construct and return the Twitter platform logger."""
if self.twitter_logger is None: if self.twitter_logger is None:
self.twitter_logger = PlatformActionLogger("twitter", self.simulation_dir) self.twitter_logger = PlatformActionLogger("twitter", self.simulation_dir)
return self.twitter_logger return self.twitter_logger
def get_reddit_logger(self) -> PlatformActionLogger: def get_reddit_logger(self) -> PlatformActionLogger:
"""获取 Reddit 平台日志记录器""" """Lazily construct and return the Reddit platform logger."""
if self.reddit_logger is None: if self.reddit_logger is None:
self.reddit_logger = PlatformActionLogger("reddit", self.simulation_dir) self.reddit_logger = PlatformActionLogger("reddit", self.simulation_dir)
return self.reddit_logger return self.reddit_logger
def log(self, message: str, level: str = "info"): def log(self, message: str, level: str = "info"):
"""记录主日志""" """Forward a message to the main logger at the given level."""
if self._main_logger: if self._main_logger:
getattr(self._main_logger, level.lower(), self._main_logger.info)(message) getattr(self._main_logger, level.lower(), self._main_logger.info)(message)
@ -196,12 +197,12 @@ class SimulationLogManager:
self.log(message, "debug") self.log(message, "debug")
# ============ 兼容旧接口 ============ # ============ Legacy interface ============
class ActionLogger: class ActionLogger:
""" """Legacy single-platform action logger.
动作日志记录器兼容旧接口
建议使用 SimulationLogManager 代替 Prefer :class:`SimulationLogManager` for new code.
""" """
def __init__(self, log_path: str): def __init__(self, log_path: str):
@ -288,12 +289,12 @@ class ActionLogger:
f.write(json.dumps(entry, ensure_ascii=False) + '\n') f.write(json.dumps(entry, ensure_ascii=False) + '\n')
# 全局日志实例(兼容旧接口) # Process-wide logger instance, used by the legacy interface.
_global_logger: Optional[ActionLogger] = None _global_logger: Optional[ActionLogger] = None
def get_logger(log_path: Optional[str] = None) -> ActionLogger: def get_logger(log_path: Optional[str] = None) -> ActionLogger:
"""获取全局日志实例(兼容旧接口)""" """Return the process-wide :class:`ActionLogger` (legacy interface)."""
global _global_logger global _global_logger
if log_path: if log_path:

View File

@ -1,8 +1,8 @@
""" """Profile-format generation tests for OASIS compatibility.
测试Profile格式生成是否符合OASIS要求
验证 Verifies that:
1. Twitter Profile生成CSV格式 1. Twitter profiles serialize to CSV format.
2. Reddit Profile生成JSON详细格式 2. Reddit profiles serialize to detailed JSON format.
""" """
import os import os
@ -11,19 +11,19 @@ import json
import csv import csv
import tempfile import tempfile
# 添加项目路径 # Add the project root to sys.path so the ``app`` package resolves.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.services.oasis_profile_generator import OasisProfileGenerator, OasisAgentProfile from app.services.oasis_profile_generator import OasisProfileGenerator, OasisAgentProfile
def test_profile_formats(): def test_profile_formats():
"""测试Profile格式""" """Exercise both profile-format outputs end-to-end."""
print("=" * 60) print("=" * 60)
print("OASIS Profile格式测试") print("OASIS Profile格式测试")
print("=" * 60) print("=" * 60)
# 创建测试Profile数据 # Build a small set of test profiles.
test_profiles = [ test_profiles = [
OasisAgentProfile( OasisAgentProfile(
user_id=0, user_id=0,
@ -63,17 +63,17 @@ def test_profile_formats():
generator = OasisProfileGenerator.__new__(OasisProfileGenerator) generator = OasisProfileGenerator.__new__(OasisProfileGenerator)
# 使用临时目录 # Use a temp directory for the test fixtures.
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
twitter_path = os.path.join(temp_dir, "twitter_profiles.csv") twitter_path = os.path.join(temp_dir, "twitter_profiles.csv")
reddit_path = os.path.join(temp_dir, "reddit_profiles.json") reddit_path = os.path.join(temp_dir, "reddit_profiles.json")
# 测试Twitter CSV格式 # Twitter CSV format.
print("\n1. 测试Twitter Profile (CSV格式)") print("\n1. 测试Twitter Profile (CSV格式)")
print("-" * 40) print("-" * 40)
generator._save_twitter_csv(test_profiles, twitter_path) generator._save_twitter_csv(test_profiles, twitter_path)
# 读取并验证CSV # Read back and verify the CSV.
with open(twitter_path, 'r', encoding='utf-8') as f: with open(twitter_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) reader = csv.DictReader(f)
rows = list(reader) rows = list(reader)
@ -85,7 +85,7 @@ def test_profile_formats():
for key, value in rows[0].items(): for key, value in rows[0].items():
print(f" {key}: {value}") print(f" {key}: {value}")
# 验证必需字段 # Verify the required fields are present.
required_twitter_fields = ['user_id', 'user_name', 'name', 'bio', required_twitter_fields = ['user_id', 'user_name', 'name', 'bio',
'friend_count', 'follower_count', 'statuses_count', 'created_at'] 'friend_count', 'follower_count', 'statuses_count', 'created_at']
missing = set(required_twitter_fields) - set(rows[0].keys()) missing = set(required_twitter_fields) - set(rows[0].keys())
@ -94,12 +94,12 @@ def test_profile_formats():
else: else:
print(f"\n [通过] 所有必需字段都存在") print(f"\n [通过] 所有必需字段都存在")
# 测试Reddit JSON格式 # Reddit JSON format.
print("\n2. 测试Reddit Profile (JSON详细格式)") print("\n2. 测试Reddit Profile (JSON详细格式)")
print("-" * 40) print("-" * 40)
generator._save_reddit_json(test_profiles, reddit_path) generator._save_reddit_json(test_profiles, reddit_path)
# 读取并验证JSON # Read back and verify the JSON.
with open(reddit_path, 'r', encoding='utf-8') as f: with open(reddit_path, 'r', encoding='utf-8') as f:
reddit_data = json.load(f) reddit_data = json.load(f)
@ -109,7 +109,7 @@ def test_profile_formats():
print(f"\n 示例数据 (第1条):") print(f"\n 示例数据 (第1条):")
print(json.dumps(reddit_data[0], ensure_ascii=False, indent=4)) print(json.dumps(reddit_data[0], ensure_ascii=False, indent=4))
# 验证详细格式字段 # Verify the detailed Reddit format fields.
required_reddit_fields = ['realname', 'username', 'bio', 'persona'] required_reddit_fields = ['realname', 'username', 'bio', 'persona']
optional_reddit_fields = ['age', 'gender', 'mbti', 'country', 'profession', 'interested_topics'] optional_reddit_fields = ['age', 'gender', 'mbti', 'country', 'profession', 'interested_topics']
@ -128,7 +128,7 @@ def test_profile_formats():
def show_expected_formats(): def show_expected_formats():
"""显示OASIS期望的格式""" """Print the canonical OASIS-expected profile formats for reference."""
print("\n" + "=" * 60) print("\n" + "=" * 60)
print("OASIS 期望的Profile格式参考") print("OASIS 期望的Profile格式参考")
print("=" * 60) print("=" * 60)