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 @@
"""
动作日志记录器
用于记录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

View File

@ -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)