Spaces:
Sleeping
Sleeping
Enhance simulation configuration and management features
Browse files- Added support for a `max_rounds` parameter in simulation API, allowing users to limit the number of simulation rounds, improving control over simulation duration.
- Updated README.md to reflect the new `max_rounds` parameter and its usage in simulation requests.
- Enhanced error handling for `max_rounds` input validation to ensure it is a positive integer.
- Modified simulation runner and related scripts to incorporate `max_rounds` functionality, ensuring consistent application across Twitter and Reddit simulations.
- Improved logging to indicate when the number of rounds is truncated due to the `max_rounds` setting, enhancing traceability during simulation execution.
- backend/README.md +11 -4
- backend/app/__init__.py +5 -0
- backend/app/api/simulation.py +24 -3
- backend/app/config.py +3 -0
- backend/app/services/simulation_config_generator.py +85 -29
- backend/app/services/simulation_runner.py +14 -1
- backend/scripts/run_parallel_simulation.py +57 -10
- backend/scripts/run_reddit_simulation.py +22 -3
- backend/scripts/run_twitter_simulation.py +22 -3
backend/README.md
CHANGED
|
@@ -554,7 +554,8 @@ backend/
|
|
| 554 |
```json
|
| 555 |
{
|
| 556 |
"simulation_id": "sim_10b494550540",
|
| 557 |
-
"platform": "parallel"
|
|
|
|
| 558 |
}
|
| 559 |
```
|
| 560 |
|
|
@@ -562,6 +563,7 @@ backend/
|
|
| 562 |
|------|------|------|--------|------|
|
| 563 |
| simulation_id | String | 是 | - | 模拟ID |
|
| 564 |
| platform | String | 否 | parallel | 运行平台: twitter/reddit/parallel |
|
|
|
|
| 565 |
|
| 566 |
**返回示例**:
|
| 567 |
```json
|
|
@@ -573,11 +575,15 @@ backend/
|
|
| 573 |
"process_pid": 12345,
|
| 574 |
"twitter_running": true,
|
| 575 |
"reddit_running": true,
|
| 576 |
-
"started_at": "2025-12-02T11:00:00"
|
|
|
|
|
|
|
| 577 |
}
|
| 578 |
}
|
| 579 |
```
|
| 580 |
|
|
|
|
|
|
|
| 581 |
---
|
| 582 |
|
| 583 |
#### 5. 停止模拟
|
|
@@ -1502,12 +1508,13 @@ curl -X POST http://localhost:5001/api/simulation/prepare/status \
|
|
| 1502 |
|
| 1503 |
# 等待status=completed
|
| 1504 |
|
| 1505 |
-
# Step 7: 启动模拟
|
| 1506 |
curl -X POST http://localhost:5001/api/simulation/start \
|
| 1507 |
-H "Content-Type: application/json" \
|
| 1508 |
-d '{
|
| 1509 |
"simulation_id": "sim_xxx",
|
| 1510 |
-
"platform": "parallel"
|
|
|
|
| 1511 |
}'
|
| 1512 |
|
| 1513 |
# Step 8: 实时查询运行状态
|
|
|
|
| 554 |
```json
|
| 555 |
{
|
| 556 |
"simulation_id": "sim_10b494550540",
|
| 557 |
+
"platform": "parallel",
|
| 558 |
+
"max_rounds": 100
|
| 559 |
}
|
| 560 |
```
|
| 561 |
|
|
|
|
| 563 |
|------|------|------|--------|------|
|
| 564 |
| simulation_id | String | 是 | - | 模拟ID |
|
| 565 |
| platform | String | 否 | parallel | 运行平台: twitter/reddit/parallel |
|
| 566 |
+
| max_rounds | Integer | 否 | - | 最大模拟轮数,用于截断过长的模拟。如果配置中的轮数超过此值,将被截断 |
|
| 567 |
|
| 568 |
**返回示例**:
|
| 569 |
```json
|
|
|
|
| 575 |
"process_pid": 12345,
|
| 576 |
"twitter_running": true,
|
| 577 |
"reddit_running": true,
|
| 578 |
+
"started_at": "2025-12-02T11:00:00",
|
| 579 |
+
"total_rounds": 100,
|
| 580 |
+
"max_rounds_applied": 100
|
| 581 |
}
|
| 582 |
}
|
| 583 |
```
|
| 584 |
|
| 585 |
+
> **说明**: `max_rounds_applied` 字段仅在指定了 `max_rounds` 参数时返回,表示实际应用的最大轮数限制。
|
| 586 |
+
|
| 587 |
---
|
| 588 |
|
| 589 |
#### 5. 停止模拟
|
|
|
|
| 1508 |
|
| 1509 |
# 等待status=completed
|
| 1510 |
|
| 1511 |
+
# Step 7: 启动模拟(可选指定max_rounds限制轮数)
|
| 1512 |
curl -X POST http://localhost:5001/api/simulation/start \
|
| 1513 |
-H "Content-Type: application/json" \
|
| 1514 |
-d '{
|
| 1515 |
"simulation_id": "sim_xxx",
|
| 1516 |
+
"platform": "parallel",
|
| 1517 |
+
"max_rounds": 50
|
| 1518 |
}'
|
| 1519 |
|
| 1520 |
# Step 8: 实时查询运行状态
|
backend/app/__init__.py
CHANGED
|
@@ -15,6 +15,11 @@ def create_app(config_class=Config):
|
|
| 15 |
app = Flask(__name__)
|
| 16 |
app.config.from_object(config_class)
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
# 设置日志
|
| 19 |
logger = setup_logger('mirofish')
|
| 20 |
|
|
|
|
| 15 |
app = Flask(__name__)
|
| 16 |
app.config.from_object(config_class)
|
| 17 |
|
| 18 |
+
# 设置JSON编码:确保中文直接显示(而不是 \uXXXX 格式)
|
| 19 |
+
# Flask >= 2.3 使用 app.json.ensure_ascii,旧版本使用 JSON_AS_ASCII 配置
|
| 20 |
+
if hasattr(app, 'json') and hasattr(app.json, 'ensure_ascii'):
|
| 21 |
+
app.json.ensure_ascii = False
|
| 22 |
+
|
| 23 |
# 设置日志
|
| 24 |
logger = setup_logger('mirofish')
|
| 25 |
|
backend/app/api/simulation.py
CHANGED
|
@@ -1114,7 +1114,8 @@ def start_simulation():
|
|
| 1114 |
请求(JSON):
|
| 1115 |
{
|
| 1116 |
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
| 1117 |
-
"platform": "parallel"
|
|
|
|
| 1118 |
}
|
| 1119 |
|
| 1120 |
返回:
|
|
@@ -1141,6 +1142,22 @@ def start_simulation():
|
|
| 1141 |
}), 400
|
| 1142 |
|
| 1143 |
platform = data.get('platform', 'parallel')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1144 |
|
| 1145 |
if platform not in ['twitter', 'reddit', 'parallel']:
|
| 1146 |
return jsonify({
|
|
@@ -1187,15 +1204,19 @@ def start_simulation():
|
|
| 1187 |
}), 400
|
| 1188 |
|
| 1189 |
# 启动模拟
|
| 1190 |
-
run_state = SimulationRunner.start_simulation(simulation_id, platform)
|
| 1191 |
|
| 1192 |
# 更新模拟状态
|
| 1193 |
state.status = SimulationStatus.RUNNING
|
| 1194 |
manager._save_simulation_state(state)
|
| 1195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1196 |
return jsonify({
|
| 1197 |
"success": True,
|
| 1198 |
-
"data":
|
| 1199 |
})
|
| 1200 |
|
| 1201 |
except ValueError as e:
|
|
|
|
| 1114 |
请求(JSON):
|
| 1115 |
{
|
| 1116 |
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
| 1117 |
+
"platform": "parallel", // 可选: twitter / reddit / parallel (默认)
|
| 1118 |
+
"max_rounds": 100 // 可选: 最大模拟轮数,用于截断过长的模拟
|
| 1119 |
}
|
| 1120 |
|
| 1121 |
返回:
|
|
|
|
| 1142 |
}), 400
|
| 1143 |
|
| 1144 |
platform = data.get('platform', 'parallel')
|
| 1145 |
+
max_rounds = data.get('max_rounds') # 可选:最大模拟轮数
|
| 1146 |
+
|
| 1147 |
+
# 验证 max_rounds 参数
|
| 1148 |
+
if max_rounds is not None:
|
| 1149 |
+
try:
|
| 1150 |
+
max_rounds = int(max_rounds)
|
| 1151 |
+
if max_rounds <= 0:
|
| 1152 |
+
return jsonify({
|
| 1153 |
+
"success": False,
|
| 1154 |
+
"error": "max_rounds 必须是正整数"
|
| 1155 |
+
}), 400
|
| 1156 |
+
except (ValueError, TypeError):
|
| 1157 |
+
return jsonify({
|
| 1158 |
+
"success": False,
|
| 1159 |
+
"error": "max_rounds 必须是有效的整数"
|
| 1160 |
+
}), 400
|
| 1161 |
|
| 1162 |
if platform not in ['twitter', 'reddit', 'parallel']:
|
| 1163 |
return jsonify({
|
|
|
|
| 1204 |
}), 400
|
| 1205 |
|
| 1206 |
# 启动模拟
|
| 1207 |
+
run_state = SimulationRunner.start_simulation(simulation_id, platform, max_rounds)
|
| 1208 |
|
| 1209 |
# 更新模拟状态
|
| 1210 |
state.status = SimulationStatus.RUNNING
|
| 1211 |
manager._save_simulation_state(state)
|
| 1212 |
|
| 1213 |
+
response_data = run_state.to_dict()
|
| 1214 |
+
if max_rounds:
|
| 1215 |
+
response_data['max_rounds_applied'] = max_rounds
|
| 1216 |
+
|
| 1217 |
return jsonify({
|
| 1218 |
"success": True,
|
| 1219 |
+
"data": response_data
|
| 1220 |
})
|
| 1221 |
|
| 1222 |
except ValueError as e:
|
backend/app/config.py
CHANGED
|
@@ -24,6 +24,9 @@ class Config:
|
|
| 24 |
SECRET_KEY = os.environ.get('SECRET_KEY', 'mirofish-secret-key')
|
| 25 |
DEBUG = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true'
|
| 26 |
|
|
|
|
|
|
|
|
|
|
| 27 |
# LLM配置(统一使用OpenAI格式)
|
| 28 |
LLM_API_KEY = os.environ.get('LLM_API_KEY')
|
| 29 |
LLM_BASE_URL = os.environ.get('LLM_BASE_URL', 'https://api.openai.com/v1')
|
|
|
|
| 24 |
SECRET_KEY = os.environ.get('SECRET_KEY', 'mirofish-secret-key')
|
| 25 |
DEBUG = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true'
|
| 26 |
|
| 27 |
+
# JSON配置 - 禁用ASCII转义,让中文直接显示(而不是 \uXXXX 格式)
|
| 28 |
+
JSON_AS_ASCII = False
|
| 29 |
+
|
| 30 |
# LLM配置(统一使用OpenAI格式)
|
| 31 |
LLM_API_KEY = os.environ.get('LLM_API_KEY')
|
| 32 |
LLM_BASE_URL = os.environ.get('LLM_BASE_URL', 'https://api.openai.com/v1')
|
backend/app/services/simulation_config_generator.py
CHANGED
|
@@ -85,8 +85,8 @@ class TimeSimulationConfig:
|
|
| 85 |
# 模拟总时长(模拟小时数)
|
| 86 |
total_simulation_hours: int = 72 # 默认模拟72小时(3天)
|
| 87 |
|
| 88 |
-
# 每轮代表的时间(模拟分钟)
|
| 89 |
-
minutes_per_round: int =
|
| 90 |
|
| 91 |
# 每小时激活的Agent数量范围
|
| 92 |
agents_per_hour_min: int = 5
|
|
@@ -205,7 +205,7 @@ class SimulationConfigGenerator:
|
|
| 205 |
|
| 206 |
采用分步生成策略:
|
| 207 |
1. 生成时间配置和事件配置(轻量级)
|
| 208 |
-
2. 分批生成Agent配置(每批10-
|
| 209 |
3. 生成平台配置
|
| 210 |
"""
|
| 211 |
|
|
@@ -214,6 +214,13 @@ class SimulationConfigGenerator:
|
|
| 214 |
# 每批生成的Agent数量
|
| 215 |
AGENTS_PER_BATCH = 15
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
def __init__(
|
| 218 |
self,
|
| 219 |
api_key: Optional[str] = None,
|
|
@@ -286,8 +293,9 @@ class SimulationConfigGenerator:
|
|
| 286 |
|
| 287 |
# ========== 步骤1: 生成时间配置 ==========
|
| 288 |
report_progress(1, "生成时间配置...")
|
| 289 |
-
|
| 290 |
-
|
|
|
|
| 291 |
reasoning_parts.append(f"时间配置: {time_config_result.get('reasoning', '成功')}")
|
| 292 |
|
| 293 |
# ========== 步骤2: 生成事件配置 ==========
|
|
@@ -411,11 +419,14 @@ class SimulationConfigGenerator:
|
|
| 411 |
|
| 412 |
for entity_type, type_entities in by_type.items():
|
| 413 |
lines.append(f"\n### {entity_type} ({len(type_entities)}个)")
|
| 414 |
-
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
| 416 |
lines.append(f"- {e.name}: {summary_preview}")
|
| 417 |
-
if len(type_entities) >
|
| 418 |
-
lines.append(f" ... 还有 {len(type_entities) -
|
| 419 |
|
| 420 |
return "\n".join(lines)
|
| 421 |
|
|
@@ -522,33 +533,56 @@ class SimulationConfigGenerator:
|
|
| 522 |
|
| 523 |
def _generate_time_config(self, context: str, num_entities: int) -> Dict[str, Any]:
|
| 524 |
"""生成时间配置"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
prompt = f"""基于以下模拟需求,生成时间模拟配置。
|
| 526 |
|
| 527 |
-
{
|
| 528 |
|
| 529 |
## 任务
|
| 530 |
-
请生成时间配置JSON
|
|
|
|
|
|
|
| 531 |
- 用户群体为中国人,需符合北京时间作息习惯
|
| 532 |
- 凌晨0-5点几乎无人活动(活跃度系数0.05)
|
| 533 |
- 早上6-8点逐渐活跃(活跃度系数0.4)
|
| 534 |
- 工作时间9-18点中等活跃(活跃度系数0.7)
|
| 535 |
- 晚间19-22点是高峰期(活跃度系数1.5)
|
| 536 |
- 23点后活跃度下降(活跃度系数0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
|
| 538 |
-
|
| 539 |
|
| 540 |
-
|
| 541 |
{{
|
| 542 |
-
"total_simulation_hours":
|
| 543 |
-
"minutes_per_round":
|
| 544 |
-
"agents_per_hour_min":
|
| 545 |
-
"agents_per_hour_max":
|
| 546 |
"peak_hours": [19, 20, 21, 22],
|
| 547 |
"off_peak_hours": [0, 1, 2, 3, 4, 5],
|
| 548 |
"morning_hours": [6, 7, 8],
|
| 549 |
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
|
| 550 |
-
"reasoning": "
|
| 551 |
-
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
|
| 553 |
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合中国人作息习惯。"
|
| 554 |
|
|
@@ -562,23 +596,41 @@ class SimulationConfigGenerator:
|
|
| 562 |
"""获取默认时间配置(中国人作息)"""
|
| 563 |
return {
|
| 564 |
"total_simulation_hours": 72,
|
| 565 |
-
"minutes_per_round":
|
| 566 |
"agents_per_hour_min": max(1, num_entities // 15),
|
| 567 |
"agents_per_hour_max": max(5, num_entities // 5),
|
| 568 |
"peak_hours": [19, 20, 21, 22],
|
| 569 |
"off_peak_hours": [0, 1, 2, 3, 4, 5],
|
| 570 |
"morning_hours": [6, 7, 8],
|
| 571 |
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
|
| 572 |
-
"reasoning": "使用默认中国人作息配置"
|
| 573 |
}
|
| 574 |
|
| 575 |
-
def _parse_time_config(self, result: Dict[str, Any]) -> TimeSimulationConfig:
|
| 576 |
-
"""解析时间配置结果"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
return TimeSimulationConfig(
|
| 578 |
total_simulation_hours=result.get("total_simulation_hours", 72),
|
| 579 |
-
minutes_per_round=result.get("minutes_per_round",
|
| 580 |
-
agents_per_hour_min=
|
| 581 |
-
agents_per_hour_max=
|
| 582 |
peak_hours=result.get("peak_hours", [19, 20, 21, 22]),
|
| 583 |
off_peak_hours=result.get("off_peak_hours", [0, 1, 2, 3, 4, 5]),
|
| 584 |
off_peak_activity_multiplier=0.05, # 凌晨几乎无人
|
|
@@ -616,11 +668,14 @@ class SimulationConfigGenerator:
|
|
| 616 |
for t, examples in type_examples.items()
|
| 617 |
])
|
| 618 |
|
|
|
|
|
|
|
|
|
|
| 619 |
prompt = f"""基于以下模拟需求,生成事件配置。
|
| 620 |
|
| 621 |
模拟需求: {simulation_requirement}
|
| 622 |
|
| 623 |
-
{
|
| 624 |
|
| 625 |
## 可用实体类型及示例
|
| 626 |
{type_info}
|
|
@@ -761,14 +816,15 @@ class SimulationConfigGenerator:
|
|
| 761 |
) -> List[AgentActivityConfig]:
|
| 762 |
"""分批生成Agent配置"""
|
| 763 |
|
| 764 |
-
# 构建实体信息
|
| 765 |
entity_list = []
|
|
|
|
| 766 |
for i, e in enumerate(entities):
|
| 767 |
entity_list.append({
|
| 768 |
"agent_id": start_idx + i,
|
| 769 |
"entity_name": e.name,
|
| 770 |
"entity_type": e.get_entity_type() or "Unknown",
|
| 771 |
-
"summary": e.summary[:
|
| 772 |
})
|
| 773 |
|
| 774 |
prompt = f"""基于以下信息,为每个实体生成社交媒体活动配置。
|
|
|
|
| 85 |
# 模拟总时长(模拟小时数)
|
| 86 |
total_simulation_hours: int = 72 # 默认模拟72小时(3天)
|
| 87 |
|
| 88 |
+
# 每轮代表的时间(模拟分钟)- 默认60分钟(1小时),加快时间流速
|
| 89 |
+
minutes_per_round: int = 60
|
| 90 |
|
| 91 |
# 每小时激活的Agent数量范围
|
| 92 |
agents_per_hour_min: int = 5
|
|
|
|
| 205 |
|
| 206 |
采用分步生成策略:
|
| 207 |
1. 生成时间配置和事件配置(轻量级)
|
| 208 |
+
2. 分批生成Agent配置(每批10-20个)
|
| 209 |
3. 生成平台配置
|
| 210 |
"""
|
| 211 |
|
|
|
|
| 214 |
# 每批生成的Agent数量
|
| 215 |
AGENTS_PER_BATCH = 15
|
| 216 |
|
| 217 |
+
# 各步骤的上下文截断长度(字符数)
|
| 218 |
+
TIME_CONFIG_CONTEXT_LENGTH = 10000 # 时间配置
|
| 219 |
+
EVENT_CONFIG_CONTEXT_LENGTH = 8000 # 事件配置
|
| 220 |
+
ENTITY_SUMMARY_LENGTH = 300 # 实体摘要
|
| 221 |
+
AGENT_SUMMARY_LENGTH = 300 # Agent配置中的实体摘要
|
| 222 |
+
ENTITIES_PER_TYPE_DISPLAY = 20 # 每类实体显示数量
|
| 223 |
+
|
| 224 |
def __init__(
|
| 225 |
self,
|
| 226 |
api_key: Optional[str] = None,
|
|
|
|
| 293 |
|
| 294 |
# ========== 步骤1: 生成时间配置 ==========
|
| 295 |
report_progress(1, "生成时间配置...")
|
| 296 |
+
num_entities = len(entities)
|
| 297 |
+
time_config_result = self._generate_time_config(context, num_entities)
|
| 298 |
+
time_config = self._parse_time_config(time_config_result, num_entities)
|
| 299 |
reasoning_parts.append(f"时间配置: {time_config_result.get('reasoning', '成功')}")
|
| 300 |
|
| 301 |
# ========== 步骤2: 生成事件配置 ==========
|
|
|
|
| 419 |
|
| 420 |
for entity_type, type_entities in by_type.items():
|
| 421 |
lines.append(f"\n### {entity_type} ({len(type_entities)}个)")
|
| 422 |
+
# 使用配置的显示数量和摘要长度
|
| 423 |
+
display_count = self.ENTITIES_PER_TYPE_DISPLAY
|
| 424 |
+
summary_len = self.ENTITY_SUMMARY_LENGTH
|
| 425 |
+
for e in type_entities[:display_count]:
|
| 426 |
+
summary_preview = (e.summary[:summary_len] + "...") if len(e.summary) > summary_len else e.summary
|
| 427 |
lines.append(f"- {e.name}: {summary_preview}")
|
| 428 |
+
if len(type_entities) > display_count:
|
| 429 |
+
lines.append(f" ... 还有 {len(type_entities) - display_count} 个")
|
| 430 |
|
| 431 |
return "\n".join(lines)
|
| 432 |
|
|
|
|
| 533 |
|
| 534 |
def _generate_time_config(self, context: str, num_entities: int) -> Dict[str, Any]:
|
| 535 |
"""生成时间配置"""
|
| 536 |
+
# 使用配置的上下文截断长度
|
| 537 |
+
context_truncated = context[:self.TIME_CONFIG_CONTEXT_LENGTH]
|
| 538 |
+
|
| 539 |
+
# 计算最大允许值(80%的agent数)
|
| 540 |
+
max_agents_allowed = max(1, int(num_entities * 0.9))
|
| 541 |
+
|
| 542 |
prompt = f"""基于以下模拟需求,生成时间模拟配置。
|
| 543 |
|
| 544 |
+
{context_truncated}
|
| 545 |
|
| 546 |
## 任务
|
| 547 |
+
请生成时间配置JSON。
|
| 548 |
+
|
| 549 |
+
### 基本原则(仅供参考,需根据具体事件和参与群体灵活调整):
|
| 550 |
- 用户群体为中国人,需符合北京时间作息习惯
|
| 551 |
- 凌晨0-5点几乎无人活动(活跃度系数0.05)
|
| 552 |
- 早上6-8点逐渐活跃(活跃度系数0.4)
|
| 553 |
- 工作时间9-18点中等活跃(活跃度系数0.7)
|
| 554 |
- 晚间19-22点是高峰期(活跃度系数1.5)
|
| 555 |
- 23点后活跃度下降(活跃度系数0.5)
|
| 556 |
+
- 一般规律:凌晨低活跃、早间渐增、工作时段中等、晚间高峰
|
| 557 |
+
- **重要**:以下示例值仅供参考,你需要根据事件性质、参与群体特点来调整具体时段
|
| 558 |
+
- 例如:学生群体高峰可能是21-23点;媒体全天活跃;官方机构只在工作时间
|
| 559 |
+
- 例如:突发热点可能导致深夜也有讨论,off_peak_hours 可适当缩短
|
| 560 |
|
| 561 |
+
### 返回JSON格式(不要markdown)
|
| 562 |
|
| 563 |
+
示例:
|
| 564 |
{{
|
| 565 |
+
"total_simulation_hours": 72,
|
| 566 |
+
"minutes_per_round": 60,
|
| 567 |
+
"agents_per_hour_min": 5,
|
| 568 |
+
"agents_per_hour_max": 50,
|
| 569 |
"peak_hours": [19, 20, 21, 22],
|
| 570 |
"off_peak_hours": [0, 1, 2, 3, 4, 5],
|
| 571 |
"morning_hours": [6, 7, 8],
|
| 572 |
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
|
| 573 |
+
"reasoning": "针对该事件的时间配置说明"
|
| 574 |
+
}}
|
| 575 |
+
|
| 576 |
+
字段说明:
|
| 577 |
+
- total_simulation_hours (int): 模拟总时长,24-168小时,突发事件短、持续话题长
|
| 578 |
+
- minutes_per_round (int): 每轮时长,30-120分钟,建议60分钟
|
| 579 |
+
- agents_per_hour_min (int): 每小时最少激活Agent数(取值范围: 1-{max_agents_allowed})
|
| 580 |
+
- agents_per_hour_max (int): 每小时最多激活Agent数(取值范围: 1-{max_agents_allowed})
|
| 581 |
+
- peak_hours (int数组): 高峰时段,根据事件参与群体调整
|
| 582 |
+
- off_peak_hours (int数组): 低谷时段,通常深夜凌晨
|
| 583 |
+
- morning_hours (int数组): 早间时段
|
| 584 |
+
- work_hours (int数组): 工作时段
|
| 585 |
+
- reasoning (string): 简要说明为什么这样配置"""
|
| 586 |
|
| 587 |
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合中国人作息习惯。"
|
| 588 |
|
|
|
|
| 596 |
"""获取默认时间配置(中国人作息)"""
|
| 597 |
return {
|
| 598 |
"total_simulation_hours": 72,
|
| 599 |
+
"minutes_per_round": 60, # 每轮1小时,加快时间流速
|
| 600 |
"agents_per_hour_min": max(1, num_entities // 15),
|
| 601 |
"agents_per_hour_max": max(5, num_entities // 5),
|
| 602 |
"peak_hours": [19, 20, 21, 22],
|
| 603 |
"off_peak_hours": [0, 1, 2, 3, 4, 5],
|
| 604 |
"morning_hours": [6, 7, 8],
|
| 605 |
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
|
| 606 |
+
"reasoning": "使用默认中国人作息配置(每轮1小时)"
|
| 607 |
}
|
| 608 |
|
| 609 |
+
def _parse_time_config(self, result: Dict[str, Any], num_entities: int) -> TimeSimulationConfig:
|
| 610 |
+
"""解析时间配置结果,并验证agents_per_hour值不超过总agent数"""
|
| 611 |
+
# 获取原始值
|
| 612 |
+
agents_per_hour_min = result.get("agents_per_hour_min", max(1, num_entities // 15))
|
| 613 |
+
agents_per_hour_max = result.get("agents_per_hour_max", max(5, num_entities // 5))
|
| 614 |
+
|
| 615 |
+
# 验证并修正:确保不超过总agent数
|
| 616 |
+
if agents_per_hour_min > num_entities:
|
| 617 |
+
logger.warning(f"agents_per_hour_min ({agents_per_hour_min}) 超过总Agent数 ({num_entities}),已修正")
|
| 618 |
+
agents_per_hour_min = max(1, num_entities // 10)
|
| 619 |
+
|
| 620 |
+
if agents_per_hour_max > num_entities:
|
| 621 |
+
logger.warning(f"agents_per_hour_max ({agents_per_hour_max}) 超过总Agent数 ({num_entities}),已修正")
|
| 622 |
+
agents_per_hour_max = max(agents_per_hour_min + 1, num_entities // 2)
|
| 623 |
+
|
| 624 |
+
# 确保 min < max
|
| 625 |
+
if agents_per_hour_min >= agents_per_hour_max:
|
| 626 |
+
agents_per_hour_min = max(1, agents_per_hour_max // 2)
|
| 627 |
+
logger.warning(f"agents_per_hour_min >= max,已修正为 {agents_per_hour_min}")
|
| 628 |
+
|
| 629 |
return TimeSimulationConfig(
|
| 630 |
total_simulation_hours=result.get("total_simulation_hours", 72),
|
| 631 |
+
minutes_per_round=result.get("minutes_per_round", 60), # 默认每轮1小时
|
| 632 |
+
agents_per_hour_min=agents_per_hour_min,
|
| 633 |
+
agents_per_hour_max=agents_per_hour_max,
|
| 634 |
peak_hours=result.get("peak_hours", [19, 20, 21, 22]),
|
| 635 |
off_peak_hours=result.get("off_peak_hours", [0, 1, 2, 3, 4, 5]),
|
| 636 |
off_peak_activity_multiplier=0.05, # 凌晨几乎无人
|
|
|
|
| 668 |
for t, examples in type_examples.items()
|
| 669 |
])
|
| 670 |
|
| 671 |
+
# 使用配置的上下文截断长度
|
| 672 |
+
context_truncated = context[:self.EVENT_CONFIG_CONTEXT_LENGTH]
|
| 673 |
+
|
| 674 |
prompt = f"""基于以下模拟需求,生成事件配置。
|
| 675 |
|
| 676 |
模拟需求: {simulation_requirement}
|
| 677 |
|
| 678 |
+
{context_truncated}
|
| 679 |
|
| 680 |
## 可用实体类型及示例
|
| 681 |
{type_info}
|
|
|
|
| 816 |
) -> List[AgentActivityConfig]:
|
| 817 |
"""分批生成Agent配置"""
|
| 818 |
|
| 819 |
+
# 构建实体信息(使用配置的摘要长度)
|
| 820 |
entity_list = []
|
| 821 |
+
summary_len = self.AGENT_SUMMARY_LENGTH
|
| 822 |
for i, e in enumerate(entities):
|
| 823 |
entity_list.append({
|
| 824 |
"agent_id": start_idx + i,
|
| 825 |
"entity_name": e.name,
|
| 826 |
"entity_type": e.get_entity_type() or "Unknown",
|
| 827 |
+
"summary": e.summary[:summary_len] if e.summary else ""
|
| 828 |
})
|
| 829 |
|
| 830 |
prompt = f"""基于以下信息,为每个实体生成社交媒体活动配置。
|
backend/app/services/simulation_runner.py
CHANGED
|
@@ -280,7 +280,8 @@ class SimulationRunner:
|
|
| 280 |
def start_simulation(
|
| 281 |
cls,
|
| 282 |
simulation_id: str,
|
| 283 |
-
platform: str = "parallel" # twitter / reddit / parallel
|
|
|
|
| 284 |
) -> SimulationRunState:
|
| 285 |
"""
|
| 286 |
启动模拟
|
|
@@ -288,6 +289,7 @@ class SimulationRunner:
|
|
| 288 |
Args:
|
| 289 |
simulation_id: 模拟ID
|
| 290 |
platform: 运行平台 (twitter/reddit/parallel)
|
|
|
|
| 291 |
|
| 292 |
Returns:
|
| 293 |
SimulationRunState
|
|
@@ -313,6 +315,13 @@ class SimulationRunner:
|
|
| 313 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 314 |
total_rounds = int(total_hours * 60 / minutes_per_round)
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
state = SimulationRunState(
|
| 317 |
simulation_id=simulation_id,
|
| 318 |
runner_status=RunnerStatus.STARTING,
|
|
@@ -358,6 +367,10 @@ class SimulationRunner:
|
|
| 358 |
"--config", config_path, # 使用完整配置文件路径
|
| 359 |
]
|
| 360 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
# 创建主日志文件,避免 stdout/stderr 管道缓冲区满导致进程阻塞
|
| 362 |
main_log_path = os.path.join(sim_dir, "simulation.log")
|
| 363 |
main_log_file = open(main_log_path, 'w', encoding='utf-8')
|
|
|
|
| 280 |
def start_simulation(
|
| 281 |
cls,
|
| 282 |
simulation_id: str,
|
| 283 |
+
platform: str = "parallel", # twitter / reddit / parallel
|
| 284 |
+
max_rounds: int = None # 最大模拟轮数(可选,用于截断过长的模拟)
|
| 285 |
) -> SimulationRunState:
|
| 286 |
"""
|
| 287 |
启动模拟
|
|
|
|
| 289 |
Args:
|
| 290 |
simulation_id: 模拟ID
|
| 291 |
platform: 运行平台 (twitter/reddit/parallel)
|
| 292 |
+
max_rounds: 最大模拟轮数(可选,用于截断过长的模拟)
|
| 293 |
|
| 294 |
Returns:
|
| 295 |
SimulationRunState
|
|
|
|
| 315 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 316 |
total_rounds = int(total_hours * 60 / minutes_per_round)
|
| 317 |
|
| 318 |
+
# 如果指定了最大轮数,则截断
|
| 319 |
+
if max_rounds is not None and max_rounds > 0:
|
| 320 |
+
original_rounds = total_rounds
|
| 321 |
+
total_rounds = min(total_rounds, max_rounds)
|
| 322 |
+
if total_rounds < original_rounds:
|
| 323 |
+
logger.info(f"轮数已截断: {original_rounds} -> {total_rounds} (max_rounds={max_rounds})")
|
| 324 |
+
|
| 325 |
state = SimulationRunState(
|
| 326 |
simulation_id=simulation_id,
|
| 327 |
runner_status=RunnerStatus.STARTING,
|
|
|
|
| 367 |
"--config", config_path, # 使用完整配置文件路径
|
| 368 |
]
|
| 369 |
|
| 370 |
+
# 如果指定了最大轮数,添加到命令行参数
|
| 371 |
+
if max_rounds is not None and max_rounds > 0:
|
| 372 |
+
cmd.extend(["--max-rounds", str(max_rounds)])
|
| 373 |
+
|
| 374 |
# 创建主日志文件,避免 stdout/stderr 管道缓冲区满导致进程阻塞
|
| 375 |
main_log_path = os.path.join(sim_dir, "simulation.log")
|
| 376 |
main_log_file = open(main_log_path, 'w', encoding='utf-8')
|
backend/scripts/run_parallel_simulation.py
CHANGED
|
@@ -404,9 +404,18 @@ async def run_twitter_simulation(
|
|
| 404 |
config: Dict[str, Any],
|
| 405 |
simulation_dir: str,
|
| 406 |
action_logger: Optional[PlatformActionLogger] = None,
|
| 407 |
-
main_logger: Optional[SimulationLogManager] = None
|
|
|
|
| 408 |
):
|
| 409 |
-
"""运行Twitter模拟
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
def log_info(msg):
|
| 411 |
if main_logger:
|
| 412 |
main_logger.info(f"[Twitter] {msg}")
|
|
@@ -494,6 +503,13 @@ async def run_twitter_simulation(
|
|
| 494 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 495 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
start_time = datetime.now()
|
| 498 |
|
| 499 |
for round_num in range(total_rounds):
|
|
@@ -552,9 +568,18 @@ async def run_reddit_simulation(
|
|
| 552 |
config: Dict[str, Any],
|
| 553 |
simulation_dir: str,
|
| 554 |
action_logger: Optional[PlatformActionLogger] = None,
|
| 555 |
-
main_logger: Optional[SimulationLogManager] = None
|
|
|
|
| 556 |
):
|
| 557 |
-
"""运行Reddit模拟
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
def log_info(msg):
|
| 559 |
if main_logger:
|
| 560 |
main_logger.info(f"[Reddit] {msg}")
|
|
@@ -649,6 +674,13 @@ async def run_reddit_simulation(
|
|
| 649 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 650 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 651 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
start_time = datetime.now()
|
| 653 |
|
| 654 |
for round_num in range(total_rounds):
|
|
@@ -721,6 +753,12 @@ async def main():
|
|
| 721 |
action='store_true',
|
| 722 |
help='只运行Reddit模拟'
|
| 723 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
|
| 725 |
args = parser.parse_args()
|
| 726 |
|
|
@@ -746,9 +784,18 @@ async def main():
|
|
| 746 |
log_manager.info("=" * 60)
|
| 747 |
|
| 748 |
time_config = config.get("time_config", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
log_manager.info(f"模拟参数:")
|
| 750 |
-
log_manager.info(f" - 总模拟时长: {
|
| 751 |
-
log_manager.info(f" - 每轮时间: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
log_manager.info(f" - Agent数量: {len(config.get('agent_configs', []))}")
|
| 753 |
|
| 754 |
log_manager.info("日志结构:")
|
|
@@ -760,14 +807,14 @@ async def main():
|
|
| 760 |
start_time = datetime.now()
|
| 761 |
|
| 762 |
if args.twitter_only:
|
| 763 |
-
await run_twitter_simulation(config, simulation_dir, twitter_logger, log_manager)
|
| 764 |
elif args.reddit_only:
|
| 765 |
-
await run_reddit_simulation(config, simulation_dir, reddit_logger, log_manager)
|
| 766 |
else:
|
| 767 |
# 并行运行(每个平台使用独立的日志记录器)
|
| 768 |
await asyncio.gather(
|
| 769 |
-
run_twitter_simulation(config, simulation_dir, twitter_logger, log_manager),
|
| 770 |
-
run_reddit_simulation(config, simulation_dir, reddit_logger, log_manager),
|
| 771 |
)
|
| 772 |
|
| 773 |
total_elapsed = (datetime.now() - start_time).total_seconds()
|
|
|
|
| 404 |
config: Dict[str, Any],
|
| 405 |
simulation_dir: str,
|
| 406 |
action_logger: Optional[PlatformActionLogger] = None,
|
| 407 |
+
main_logger: Optional[SimulationLogManager] = None,
|
| 408 |
+
max_rounds: Optional[int] = None
|
| 409 |
):
|
| 410 |
+
"""运行Twitter模拟
|
| 411 |
+
|
| 412 |
+
Args:
|
| 413 |
+
config: 模拟配置
|
| 414 |
+
simulation_dir: 模拟目录
|
| 415 |
+
action_logger: 动作日志记录器
|
| 416 |
+
main_logger: 主日志管理器
|
| 417 |
+
max_rounds: 最大模拟轮数(可选,用于截断过长的模拟)
|
| 418 |
+
"""
|
| 419 |
def log_info(msg):
|
| 420 |
if main_logger:
|
| 421 |
main_logger.info(f"[Twitter] {msg}")
|
|
|
|
| 503 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 504 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 505 |
|
| 506 |
+
# 如果指定了最大轮数,则截断
|
| 507 |
+
if max_rounds is not None and max_rounds > 0:
|
| 508 |
+
original_rounds = total_rounds
|
| 509 |
+
total_rounds = min(total_rounds, max_rounds)
|
| 510 |
+
if total_rounds < original_rounds:
|
| 511 |
+
log_info(f"轮数已截断: {original_rounds} -> {total_rounds} (max_rounds={max_rounds})")
|
| 512 |
+
|
| 513 |
start_time = datetime.now()
|
| 514 |
|
| 515 |
for round_num in range(total_rounds):
|
|
|
|
| 568 |
config: Dict[str, Any],
|
| 569 |
simulation_dir: str,
|
| 570 |
action_logger: Optional[PlatformActionLogger] = None,
|
| 571 |
+
main_logger: Optional[SimulationLogManager] = None,
|
| 572 |
+
max_rounds: Optional[int] = None
|
| 573 |
):
|
| 574 |
+
"""运行Reddit模拟
|
| 575 |
+
|
| 576 |
+
Args:
|
| 577 |
+
config: 模拟配置
|
| 578 |
+
simulation_dir: 模拟目录
|
| 579 |
+
action_logger: 动作日志记录器
|
| 580 |
+
main_logger: 主日志管理器
|
| 581 |
+
max_rounds: 最大模拟轮数(可选,用于截断过长的模拟)
|
| 582 |
+
"""
|
| 583 |
def log_info(msg):
|
| 584 |
if main_logger:
|
| 585 |
main_logger.info(f"[Reddit] {msg}")
|
|
|
|
| 674 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 675 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 676 |
|
| 677 |
+
# 如果指定了最大轮数,则截断
|
| 678 |
+
if max_rounds is not None and max_rounds > 0:
|
| 679 |
+
original_rounds = total_rounds
|
| 680 |
+
total_rounds = min(total_rounds, max_rounds)
|
| 681 |
+
if total_rounds < original_rounds:
|
| 682 |
+
log_info(f"轮数已截断: {original_rounds} -> {total_rounds} (max_rounds={max_rounds})")
|
| 683 |
+
|
| 684 |
start_time = datetime.now()
|
| 685 |
|
| 686 |
for round_num in range(total_rounds):
|
|
|
|
| 753 |
action='store_true',
|
| 754 |
help='只运行Reddit模拟'
|
| 755 |
)
|
| 756 |
+
parser.add_argument(
|
| 757 |
+
'--max-rounds',
|
| 758 |
+
type=int,
|
| 759 |
+
default=None,
|
| 760 |
+
help='最大模拟轮数(可选,用于截断过长的模拟)'
|
| 761 |
+
)
|
| 762 |
|
| 763 |
args = parser.parse_args()
|
| 764 |
|
|
|
|
| 784 |
log_manager.info("=" * 60)
|
| 785 |
|
| 786 |
time_config = config.get("time_config", {})
|
| 787 |
+
total_hours = time_config.get('total_simulation_hours', 72)
|
| 788 |
+
minutes_per_round = time_config.get('minutes_per_round', 30)
|
| 789 |
+
config_total_rounds = (total_hours * 60) // minutes_per_round
|
| 790 |
+
|
| 791 |
log_manager.info(f"模拟参数:")
|
| 792 |
+
log_manager.info(f" - 总模拟时长: {total_hours}小时")
|
| 793 |
+
log_manager.info(f" - 每轮时间: {minutes_per_round}分钟")
|
| 794 |
+
log_manager.info(f" - 配置总轮数: {config_total_rounds}")
|
| 795 |
+
if args.max_rounds:
|
| 796 |
+
log_manager.info(f" - 最大轮数限制: {args.max_rounds}")
|
| 797 |
+
if args.max_rounds < config_total_rounds:
|
| 798 |
+
log_manager.info(f" - 实际执行轮数: {args.max_rounds} (已截断)")
|
| 799 |
log_manager.info(f" - Agent数量: {len(config.get('agent_configs', []))}")
|
| 800 |
|
| 801 |
log_manager.info("日志结构:")
|
|
|
|
| 807 |
start_time = datetime.now()
|
| 808 |
|
| 809 |
if args.twitter_only:
|
| 810 |
+
await run_twitter_simulation(config, simulation_dir, twitter_logger, log_manager, args.max_rounds)
|
| 811 |
elif args.reddit_only:
|
| 812 |
+
await run_reddit_simulation(config, simulation_dir, reddit_logger, log_manager, args.max_rounds)
|
| 813 |
else:
|
| 814 |
# 并行运行(每个平台使用独立的日志记录器)
|
| 815 |
await asyncio.gather(
|
| 816 |
+
run_twitter_simulation(config, simulation_dir, twitter_logger, log_manager, args.max_rounds),
|
| 817 |
+
run_reddit_simulation(config, simulation_dir, reddit_logger, log_manager, args.max_rounds),
|
| 818 |
)
|
| 819 |
|
| 820 |
total_elapsed = (datetime.now() - start_time).total_seconds()
|
backend/scripts/run_reddit_simulation.py
CHANGED
|
@@ -251,8 +251,12 @@ class RedditSimulationRunner:
|
|
| 251 |
|
| 252 |
return active_agents
|
| 253 |
|
| 254 |
-
async def run(self):
|
| 255 |
-
"""运行Reddit模拟
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
print("=" * 60)
|
| 257 |
print("OASIS Reddit模拟")
|
| 258 |
print(f"配置文件: {self.config_path}")
|
|
@@ -264,10 +268,19 @@ class RedditSimulationRunner:
|
|
| 264 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 265 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
print(f"\n模拟参数:")
|
| 268 |
print(f" - 总模拟时长: {total_hours}小时")
|
| 269 |
print(f" - 每轮时间: {minutes_per_round}分钟")
|
| 270 |
print(f" - 总轮数: {total_rounds}")
|
|
|
|
|
|
|
| 271 |
print(f" - Agent数量: {len(self.config.get('agent_configs', []))}")
|
| 272 |
|
| 273 |
print("\n初始化LLM模型...")
|
|
@@ -380,6 +393,12 @@ async def main():
|
|
| 380 |
required=True,
|
| 381 |
help='配置文件路径 (simulation_config.json)'
|
| 382 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
|
| 384 |
args = parser.parse_args()
|
| 385 |
|
|
@@ -392,7 +411,7 @@ async def main():
|
|
| 392 |
setup_oasis_logging(os.path.join(simulation_dir, "log"))
|
| 393 |
|
| 394 |
runner = RedditSimulationRunner(args.config)
|
| 395 |
-
await runner.run()
|
| 396 |
|
| 397 |
|
| 398 |
if __name__ == "__main__":
|
|
|
|
| 251 |
|
| 252 |
return active_agents
|
| 253 |
|
| 254 |
+
async def run(self, max_rounds: int = None):
|
| 255 |
+
"""运行Reddit模拟
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
max_rounds: 最大模拟轮数(可选,用于截断过长的模拟)
|
| 259 |
+
"""
|
| 260 |
print("=" * 60)
|
| 261 |
print("OASIS Reddit模拟")
|
| 262 |
print(f"配置文件: {self.config_path}")
|
|
|
|
| 268 |
minutes_per_round = time_config.get("minutes_per_round", 30)
|
| 269 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 270 |
|
| 271 |
+
# 如果指定了最大轮数,则截断
|
| 272 |
+
if max_rounds is not None and max_rounds > 0:
|
| 273 |
+
original_rounds = total_rounds
|
| 274 |
+
total_rounds = min(total_rounds, max_rounds)
|
| 275 |
+
if total_rounds < original_rounds:
|
| 276 |
+
print(f"\n轮数已截断: {original_rounds} -> {total_rounds} (max_rounds={max_rounds})")
|
| 277 |
+
|
| 278 |
print(f"\n模拟参数:")
|
| 279 |
print(f" - 总模拟时长: {total_hours}小时")
|
| 280 |
print(f" - 每轮时间: {minutes_per_round}分钟")
|
| 281 |
print(f" - 总轮数: {total_rounds}")
|
| 282 |
+
if max_rounds:
|
| 283 |
+
print(f" - 最大轮数限制: {max_rounds}")
|
| 284 |
print(f" - Agent数量: {len(self.config.get('agent_configs', []))}")
|
| 285 |
|
| 286 |
print("\n初始化LLM模型...")
|
|
|
|
| 393 |
required=True,
|
| 394 |
help='配置文件路径 (simulation_config.json)'
|
| 395 |
)
|
| 396 |
+
parser.add_argument(
|
| 397 |
+
'--max-rounds',
|
| 398 |
+
type=int,
|
| 399 |
+
default=None,
|
| 400 |
+
help='最大模拟轮数(可选,用于截断过长的模拟)'
|
| 401 |
+
)
|
| 402 |
|
| 403 |
args = parser.parse_args()
|
| 404 |
|
|
|
|
| 411 |
setup_oasis_logging(os.path.join(simulation_dir, "log"))
|
| 412 |
|
| 413 |
runner = RedditSimulationRunner(args.config)
|
| 414 |
+
await runner.run(max_rounds=args.max_rounds)
|
| 415 |
|
| 416 |
|
| 417 |
if __name__ == "__main__":
|
backend/scripts/run_twitter_simulation.py
CHANGED
|
@@ -259,8 +259,12 @@ class TwitterSimulationRunner:
|
|
| 259 |
|
| 260 |
return active_agents
|
| 261 |
|
| 262 |
-
async def run(self):
|
| 263 |
-
"""运行Twitter模拟
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
print("=" * 60)
|
| 265 |
print("OASIS Twitter模拟")
|
| 266 |
print(f"配置文件: {self.config_path}")
|
|
@@ -275,10 +279,19 @@ class TwitterSimulationRunner:
|
|
| 275 |
# 计算总轮数
|
| 276 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
print(f"\n模拟参数:")
|
| 279 |
print(f" - 总模拟时长: {total_hours}小时")
|
| 280 |
print(f" - 每轮时间: {minutes_per_round}分钟")
|
| 281 |
print(f" - 总轮数: {total_rounds}")
|
|
|
|
|
|
|
| 282 |
print(f" - Agent数量: {len(self.config.get('agent_configs', []))}")
|
| 283 |
|
| 284 |
# 创建模型
|
|
@@ -393,6 +406,12 @@ async def main():
|
|
| 393 |
required=True,
|
| 394 |
help='配置文件路径 (simulation_config.json)'
|
| 395 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
args = parser.parse_args()
|
| 398 |
|
|
@@ -405,7 +424,7 @@ async def main():
|
|
| 405 |
setup_oasis_logging(os.path.join(simulation_dir, "log"))
|
| 406 |
|
| 407 |
runner = TwitterSimulationRunner(args.config)
|
| 408 |
-
await runner.run()
|
| 409 |
|
| 410 |
|
| 411 |
if __name__ == "__main__":
|
|
|
|
| 259 |
|
| 260 |
return active_agents
|
| 261 |
|
| 262 |
+
async def run(self, max_rounds: int = None):
|
| 263 |
+
"""运行Twitter模拟
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
max_rounds: 最大模拟轮数(可选,用于截断过长的模拟)
|
| 267 |
+
"""
|
| 268 |
print("=" * 60)
|
| 269 |
print("OASIS Twitter模拟")
|
| 270 |
print(f"配置文件: {self.config_path}")
|
|
|
|
| 279 |
# 计算总轮数
|
| 280 |
total_rounds = (total_hours * 60) // minutes_per_round
|
| 281 |
|
| 282 |
+
# 如果指定了最大轮数,则截断
|
| 283 |
+
if max_rounds is not None and max_rounds > 0:
|
| 284 |
+
original_rounds = total_rounds
|
| 285 |
+
total_rounds = min(total_rounds, max_rounds)
|
| 286 |
+
if total_rounds < original_rounds:
|
| 287 |
+
print(f"\n轮数已截断: {original_rounds} -> {total_rounds} (max_rounds={max_rounds})")
|
| 288 |
+
|
| 289 |
print(f"\n模拟参数:")
|
| 290 |
print(f" - 总模拟时长: {total_hours}小时")
|
| 291 |
print(f" - 每轮时间: {minutes_per_round}分钟")
|
| 292 |
print(f" - 总轮数: {total_rounds}")
|
| 293 |
+
if max_rounds:
|
| 294 |
+
print(f" - 最大轮数限制: {max_rounds}")
|
| 295 |
print(f" - Agent数量: {len(self.config.get('agent_configs', []))}")
|
| 296 |
|
| 297 |
# 创建模型
|
|
|
|
| 406 |
required=True,
|
| 407 |
help='配置文件路径 (simulation_config.json)'
|
| 408 |
)
|
| 409 |
+
parser.add_argument(
|
| 410 |
+
'--max-rounds',
|
| 411 |
+
type=int,
|
| 412 |
+
default=None,
|
| 413 |
+
help='最大模拟轮数(可选,用于截断过长的模拟)'
|
| 414 |
+
)
|
| 415 |
|
| 416 |
args = parser.parse_args()
|
| 417 |
|
|
|
|
| 424 |
setup_oasis_logging(os.path.join(simulation_dir, "log"))
|
| 425 |
|
| 426 |
runner = TwitterSimulationRunner(args.config)
|
| 427 |
+
await runner.run(max_rounds=args.max_rounds)
|
| 428 |
|
| 429 |
|
| 430 |
if __name__ == "__main__":
|