From c8c455ceb41b43b31bf4addb846542461c30ce06 Mon Sep 17 00:00:00 2001 From: Dominik Seemann Date: Thu, 7 May 2026 14:51:05 +0000 Subject: [PATCH] docs(i18n): translate chinese docstrings/comments in backend/scripts/{test_profile_format,action_logger} --- backend/scripts/action_logger.py | 165 +++++++++++++------------ backend/scripts/test_profile_format.py | 44 +++---- 2 files changed, 105 insertions(+), 104 deletions(-) diff --git a/backend/scripts/action_logger.py b/backend/scripts/action_logger.py index 38d025a6..bea32e20 100644 --- a/backend/scripts/action_logger.py +++ b/backend/scripts/action_logger.py @@ -1,15 +1,17 @@ -""" -动作日志记录器 -用于记录OASIS模拟中每个Agent的动作,供后端监控使用 +"""Action logger. + +Records each agent action during an OASIS simulation so the backend can +monitor progress. + +Log layout:: -日志结构: sim_xxx/ ├── twitter/ - │ └── actions.jsonl # Twitter 平台动作日志 + │ └── actions.jsonl # Twitter action log ├── reddit/ - │ └── actions.jsonl # Reddit 平台动作日志 - ├── simulation.log # 主模拟进程日志 - └── run_state.json # 运行状态(API 查询用) + │ └── actions.jsonl # Reddit action log + ├── simulation.log # main simulation process log + └── run_state.json # run state (queried by the API) """ import json @@ -20,26 +22,25 @@ from typing import Dict, Any, Optional class PlatformActionLogger: - """单平台动作日志记录器""" - + """Per-platform action logger.""" + def __init__(self, platform: str, base_dir: str): - """ - 初始化日志记录器 - + """Initialize the logger. + Args: - platform: 平台名称 (twitter/reddit) - base_dir: 模拟目录的基础路径 + platform: Platform name (``twitter`` or ``reddit``). + base_dir: Base path of the simulation directory. """ self.platform = platform self.base_dir = base_dir self.log_dir = os.path.join(base_dir, platform) self.log_path = os.path.join(self.log_dir, "actions.jsonl") self._ensure_dir() - + def _ensure_dir(self): - """确保目录存在""" + """Ensure the log directory exists.""" os.makedirs(self.log_dir, exist_ok=True) - + def log_action( self, round_num: int, @@ -50,7 +51,7 @@ class PlatformActionLogger: result: Optional[str] = None, success: bool = True ): - """记录一个动作""" + """Append a single action record.""" entry = { "round": round_num, "timestamp": datetime.now().isoformat(), @@ -61,36 +62,36 @@ class PlatformActionLogger: "result": result, "success": success, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_round_start(self, round_num: int, simulated_hour: int): - """记录轮次开始""" + """Append a round-start marker.""" entry = { "round": round_num, "timestamp": datetime.now().isoformat(), "event_type": "round_start", "simulated_hour": simulated_hour, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_round_end(self, round_num: int, actions_count: int): - """记录轮次结束""" + """Append a round-end marker.""" entry = { "round": round_num, "timestamp": datetime.now().isoformat(), "event_type": "round_end", "actions_count": actions_count, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_simulation_start(self, config: Dict[str, Any]): - """记录模拟开始""" + """Append a simulation-start marker.""" entry = { "timestamp": datetime.now().isoformat(), "event_type": "simulation_start", @@ -98,12 +99,12 @@ class PlatformActionLogger: "total_rounds": config.get("time_config", {}).get("total_simulation_hours", 72) * 2, "agents_count": len(config.get("agent_configs", [])), } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_simulation_end(self, total_rounds: int, total_actions: int): - """记录模拟结束""" + """Append a simulation-end marker.""" entry = { "timestamp": datetime.now().isoformat(), "event_type": "simulation_end", @@ -111,42 +112,42 @@ class PlatformActionLogger: "total_rounds": total_rounds, "total_actions": total_actions, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') 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): - """ - 初始化日志管理器 - + """Initialize the log manager. + Args: - simulation_dir: 模拟目录路径 + simulation_dir: Path to the simulation directory. """ self.simulation_dir = simulation_dir self.twitter_logger: Optional[PlatformActionLogger] = None self.reddit_logger: Optional[PlatformActionLogger] = None self._main_logger: Optional[logging.Logger] = None - - # 设置主日志 + + # Configure the main process logger. self._setup_main_logger() - + def _setup_main_logger(self): - """设置主模拟日志""" + """Configure the main 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.setLevel(logging.INFO) self._main_logger.handlers.clear() - - # 文件处理器 + + # File handler. file_handler = logging.FileHandler(log_path, encoding='utf-8', mode='w') file_handler.setLevel(logging.INFO) file_handler.setFormatter(logging.Formatter( @@ -154,8 +155,8 @@ class SimulationLogManager: datefmt='%Y-%m-%d %H:%M:%S' )) self._main_logger.addHandler(file_handler) - - # 控制台处理器 + + # Console handler. console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) console_handler.setFormatter(logging.Formatter( @@ -163,56 +164,56 @@ class SimulationLogManager: datefmt='%H:%M:%S' )) self._main_logger.addHandler(console_handler) - + self._main_logger.propagate = False - + def get_twitter_logger(self) -> PlatformActionLogger: - """获取 Twitter 平台日志记录器""" + """Lazily construct and return the Twitter platform logger.""" if self.twitter_logger is None: self.twitter_logger = PlatformActionLogger("twitter", self.simulation_dir) return self.twitter_logger - + def get_reddit_logger(self) -> PlatformActionLogger: - """获取 Reddit 平台日志记录器""" + """Lazily construct and return the Reddit platform logger.""" if self.reddit_logger is None: self.reddit_logger = PlatformActionLogger("reddit", self.simulation_dir) return self.reddit_logger - + def log(self, message: str, level: str = "info"): - """记录主日志""" + """Forward a message to the main logger at the given level.""" if self._main_logger: getattr(self._main_logger, level.lower(), self._main_logger.info)(message) - + def info(self, message: str): self.log(message, "info") - + def warning(self, message: str): self.log(message, "warning") - + def error(self, message: str): self.log(message, "error") - + def debug(self, message: str): self.log(message, "debug") -# ============ 兼容旧接口 ============ +# ============ Legacy interface ============ class ActionLogger: + """Legacy single-platform action logger. + + Prefer :class:`SimulationLogManager` for new code. """ - 动作日志记录器(兼容旧接口) - 建议使用 SimulationLogManager 代替 - """ - + def __init__(self, log_path: str): self.log_path = log_path self._ensure_dir() - + def _ensure_dir(self): log_dir = os.path.dirname(self.log_path) if log_dir: os.makedirs(log_dir, exist_ok=True) - + def log_action( self, round_num: int, @@ -235,10 +236,10 @@ class ActionLogger: "result": result, "success": success, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_round_start(self, round_num: int, simulated_hour: int, platform: str): entry = { "round": round_num, @@ -247,10 +248,10 @@ class ActionLogger: "event_type": "round_start", "simulated_hour": simulated_hour, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_round_end(self, round_num: int, actions_count: int, platform: str): entry = { "round": round_num, @@ -259,10 +260,10 @@ class ActionLogger: "event_type": "round_end", "actions_count": actions_count, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_simulation_start(self, platform: str, config: Dict[str, Any]): entry = { "timestamp": datetime.now().isoformat(), @@ -271,10 +272,10 @@ class ActionLogger: "total_rounds": config.get("time_config", {}).get("total_simulation_hours", 72) * 2, "agents_count": len(config.get("agent_configs", [])), } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') - + def log_simulation_end(self, platform: str, total_rounds: int, total_actions: int): entry = { "timestamp": datetime.now().isoformat(), @@ -283,23 +284,23 @@ class ActionLogger: "total_rounds": total_rounds, "total_actions": total_actions, } - + with open(self.log_path, 'a', encoding='utf-8') as f: f.write(json.dumps(entry, ensure_ascii=False) + '\n') -# 全局日志实例(兼容旧接口) +# Process-wide logger instance, used by the legacy interface. _global_logger: Optional[ActionLogger] = None def get_logger(log_path: Optional[str] = None) -> ActionLogger: - """获取全局日志实例(兼容旧接口)""" + """Return the process-wide :class:`ActionLogger` (legacy interface).""" global _global_logger - + if log_path: _global_logger = ActionLogger(log_path) - + if _global_logger is None: _global_logger = ActionLogger("actions.jsonl") - + return _global_logger diff --git a/backend/scripts/test_profile_format.py b/backend/scripts/test_profile_format.py index 354e8b5c..5e312e60 100644 --- a/backend/scripts/test_profile_format.py +++ b/backend/scripts/test_profile_format.py @@ -1,8 +1,8 @@ -""" -测试Profile格式生成是否符合OASIS要求 -验证: -1. Twitter Profile生成CSV格式 -2. Reddit Profile生成JSON详细格式 +"""Profile-format generation tests for OASIS compatibility. + +Verifies that: +1. Twitter profiles serialize to CSV format. +2. Reddit profiles serialize to detailed JSON format. """ import os @@ -11,19 +11,19 @@ import json import csv 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__)))) from app.services.oasis_profile_generator import OasisProfileGenerator, OasisAgentProfile def test_profile_formats(): - """测试Profile格式""" + """Exercise both profile-format outputs end-to-end.""" print("=" * 60) print("OASIS Profile格式测试") print("=" * 60) - - # 创建测试Profile数据 + + # Build a small set of test profiles. test_profiles = [ OasisAgentProfile( user_id=0, @@ -62,18 +62,18 @@ def test_profile_formats(): ] generator = OasisProfileGenerator.__new__(OasisProfileGenerator) - - # 使用临时目录 + + # Use a temp directory for the test fixtures. with tempfile.TemporaryDirectory() as temp_dir: twitter_path = os.path.join(temp_dir, "twitter_profiles.csv") reddit_path = os.path.join(temp_dir, "reddit_profiles.json") - - # 测试Twitter CSV格式 + + # Twitter CSV format. print("\n1. 测试Twitter Profile (CSV格式)") print("-" * 40) 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: reader = csv.DictReader(f) rows = list(reader) @@ -85,8 +85,8 @@ def test_profile_formats(): for key, value in rows[0].items(): print(f" {key}: {value}") - # 验证必需字段 - required_twitter_fields = ['user_id', 'user_name', 'name', 'bio', + # Verify the required fields are present. + required_twitter_fields = ['user_id', 'user_name', 'name', 'bio', 'friend_count', 'follower_count', 'statuses_count', 'created_at'] missing = set(required_twitter_fields) - set(rows[0].keys()) if missing: @@ -94,12 +94,12 @@ def test_profile_formats(): else: print(f"\n [通过] 所有必需字段都存在") - # 测试Reddit JSON格式 + # Reddit JSON format. print("\n2. 测试Reddit Profile (JSON详细格式)") print("-" * 40) 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: reddit_data = json.load(f) @@ -109,7 +109,7 @@ def test_profile_formats(): print(f"\n 示例数据 (第1条):") print(json.dumps(reddit_data[0], ensure_ascii=False, indent=4)) - # 验证详细格式字段 + # Verify the detailed Reddit format fields. required_reddit_fields = ['realname', 'username', 'bio', 'persona'] optional_reddit_fields = ['age', 'gender', 'mbti', 'country', 'profession', 'interested_topics'] @@ -128,7 +128,7 @@ def test_profile_formats(): def show_expected_formats(): - """显示OASIS期望的格式""" + """Print the canonical OASIS-expected profile formats for reference.""" print("\n" + "=" * 60) print("OASIS 期望的Profile格式参考") print("=" * 60)