Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/app/api/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ def _build_graph_impl():
}), 500

# 解析请求
data = request.get_json() or {}
data = request.get_json(silent=True) or {}
project_id = data.get('project_id')
logger.debug(f"请求参数: project_id={project_id}")

Expand Down
10 changes: 5 additions & 5 deletions backend/app/api/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def generate_report():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
if not simulation_id:
Expand Down Expand Up @@ -342,7 +342,7 @@ def get_generate_status():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

task_id = data.get('task_id')
simulation_id = data.get('simulation_id')
Expand Down Expand Up @@ -616,7 +616,7 @@ def chat_with_report_agent():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
message = data.get('message')
Expand Down Expand Up @@ -1064,7 +1064,7 @@ def search_graph_tool():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

graph_id = data.get('graph_id')
query = data.get('query')
Expand Down Expand Up @@ -1110,7 +1110,7 @@ def get_graph_statistics_tool():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

graph_id = data.get('graph_id')

Expand Down
67 changes: 51 additions & 16 deletions backend/app/api/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def create_simulation():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

project_id = data.get('project_id')
if not project_id:
Expand Down Expand Up @@ -321,7 +321,7 @@ def _check_simulation_prepared(simulation_id: str) -> tuple:
state_file = os.path.join(simulation_dir, "state.json")
try:
import json
with open(state_file, 'r', encoding='utf-8') as f:
with open(state_file, 'r', encoding='utf-8-sig') as f:
state_data = json.load(f)

status = state_data.get("status", "")
Expand All @@ -337,8 +337,9 @@ def _check_simulation_prepared(simulation_id: str) -> tuple:
# - running: 正在运行,说明准备早就完成了
# - completed: 运行完成,说明准备早就完成了
# - stopped: 已停止,说明准备早就完成了
# - paused: 手动停止后会写入 paused,配置仍然可复用
# - failed: 运行失败(但准备是完成的)
prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "failed"]
prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "paused", "failed"]
if status in prepared_statuses and config_generated:
# 获取文件统计信息
profiles_file = os.path.join(simulation_dir, "reddit_profiles.json")
Expand Down Expand Up @@ -433,7 +434,7 @@ def prepare_simulation():
from ..config import Config

try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
if not simulation_id:
Expand Down Expand Up @@ -705,7 +706,7 @@ def get_prepare_status():
from ..models.task import TaskManager

try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

task_id = data.get('task_id')
simulation_id = data.get('simulation_id')
Expand Down Expand Up @@ -1141,7 +1142,7 @@ def get_simulation_profiles_realtime(simulation_id: str):
state_file = os.path.join(sim_dir, "state.json")
if os.path.exists(state_file):
try:
with open(state_file, 'r', encoding='utf-8') as f:
with open(state_file, 'r', encoding='utf-8-sig') as f:
state_data = json.load(f)
status = state_data.get("status", "")
is_generating = status == "preparing"
Expand Down Expand Up @@ -1243,7 +1244,7 @@ def get_simulation_config_realtime(simulation_id: str):
state_file = os.path.join(sim_dir, "state.json")
if os.path.exists(state_file):
try:
with open(state_file, 'r', encoding='utf-8') as f:
with open(state_file, 'r', encoding='utf-8-sig') as f:
state_data = json.load(f)
status = state_data.get("status", "")
error = state_data.get("error")
Expand Down Expand Up @@ -1438,7 +1439,7 @@ def generate_profiles():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

graph_id = data.get('graph_id')
if not graph_id:
Expand Down Expand Up @@ -1540,7 +1541,7 @@ def start_simulation():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
if not simulation_id:
Expand Down Expand Up @@ -1691,6 +1692,40 @@ def start_simulation():
"error": t('api.graphIdRequiredForMemory')
}), 400

existing_run_state = SimulationRunner.get_run_state(simulation_id)
restartable_statuses = {
RunnerStatus.IDLE,
RunnerStatus.STOPPED,
RunnerStatus.COMPLETED,
RunnerStatus.FAILED,
}
if (
existing_run_state
and existing_run_state.runner_status in restartable_statuses
):
if ZepGraphMemoryManager.get_updater(simulation_id) is not None:
return jsonify({
"success": False,
"error": (
"The previous simulation still has pending graph "
"memory updates; finalize or reset it before restarting"
),
}), 409
logger.info(
f"清理已结束的旧运行记录后重新启动: "
f"simulation_id={simulation_id}, runner_status={existing_run_state.runner_status.value}"
)
cleanup_result = SimulationRunner.cleanup_simulation_logs(simulation_id)
if not cleanup_result.get("success"):
return jsonify({
"success": False,
"error": (
"Failed to clean previous simulation logs: "
f"{cleanup_result.get('errors')}"
),
}), 500
force_restarted = True

graph_guard = (
graph_lifecycle_lock(graph_id)
if enable_graph_memory_update
Expand Down Expand Up @@ -1806,7 +1841,7 @@ def stop_simulation():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
if not simulation_id:
Expand Down Expand Up @@ -2353,7 +2388,7 @@ def interview_agent():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
agent_id = data.get('agent_id')
Expand Down Expand Up @@ -2475,7 +2510,7 @@ def interview_agents_batch():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
interviews = data.get('interviews')
Expand Down Expand Up @@ -2602,7 +2637,7 @@ def interview_all_agents():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
prompt = data.get('prompt')
Expand Down Expand Up @@ -2706,7 +2741,7 @@ def get_interview_history():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
platform = data.get('platform') # 不指定则返回两个平台的历史
Expand Down Expand Up @@ -2768,7 +2803,7 @@ def get_env_status():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')

Expand Down Expand Up @@ -2835,7 +2870,7 @@ def close_simulation_env():
}
"""
try:
data = request.get_json() or {}
data = request.get_json(silent=True) or {}

simulation_id = data.get('simulation_id')
timeout = data.get('timeout', 30)
Expand Down
3 changes: 2 additions & 1 deletion backend/app/services/oasis_profile_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ def __init__(

self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
base_url=self.base_url,
default_headers={"User-Agent": "python-requests/2.32.5"}
)

# Zep客户端用于检索丰富上下文
Expand Down
3 changes: 2 additions & 1 deletion backend/app/services/simulation_config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ def __init__(

self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
base_url=self.base_url,
default_headers={"User-Agent": "python-requests/2.32.5"}
)

def generate_config(
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/simulation_ipc.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def check_env_alive(self) -> bool:
return False

try:
with open(status_file, 'r', encoding='utf-8') as f:
with open(status_file, 'r', encoding='utf-8-sig') as f:
status = json.load(f)
return status.get("status") == "alive"
except (json.JSONDecodeError, OSError):
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/simulation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def _load_simulation_state(self, simulation_id: str) -> Optional[SimulationState
if not os.path.exists(state_file):
return None

with open(state_file, 'r', encoding='utf-8') as f:
with open(state_file, 'r', encoding='utf-8-sig') as f:
data = json.load(f)

state = SimulationState(
Expand Down
36 changes: 27 additions & 9 deletions backend/app/services/simulation_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import subprocess
import signal
import atexit
import psutil
from typing import Dict, Any, List, Optional, Union
from dataclasses import dataclass, field
from datetime import datetime
Expand Down Expand Up @@ -305,7 +306,7 @@ def _load_run_state(cls, simulation_id: str) -> Optional[SimulationRunState]:
return None

try:
with open(state_file, 'r', encoding='utf-8') as f:
with open(state_file, 'r', encoding='utf-8-sig') as f:
data = json.load(f)

state = SimulationRunState(
Expand Down Expand Up @@ -1652,7 +1653,23 @@ def check_env_alive(cls, simulation_id: str) -> bool:
return False

ipc_client = SimulationIPCClient(sim_dir)
return ipc_client.check_env_alive()
if not ipc_client.check_env_alive():
return False

process = cls._processes.get(simulation_id)
if process is not None:
return process.poll() is None

state = cls.get_run_state(simulation_id)
pid = state.process_pid if state else None
if not pid:
return False

try:
proc = psutil.Process(pid)
return proc.is_running() and proc.status() != psutil.STATUS_ZOMBIE
except psutil.Error:
return False

@classmethod
def get_env_status_detail(cls, simulation_id: str) -> Dict[str, Any]:
Expand All @@ -1679,12 +1696,13 @@ def get_env_status_detail(cls, simulation_id: str) -> Dict[str, Any]:
return default_status

try:
with open(status_file, 'r', encoding='utf-8') as f:
with open(status_file, 'r', encoding='utf-8-sig') as f:
status = json.load(f)
env_alive = cls.check_env_alive(simulation_id)
return {
"status": status.get("status", "stopped"),
"twitter_available": status.get("twitter_available", False),
"reddit_available": status.get("reddit_available", False),
"status": status.get("status", "stopped") if env_alive else "stopped",
"twitter_available": status.get("twitter_available", False) if env_alive else False,
"reddit_available": status.get("reddit_available", False) if env_alive else False,
"timestamp": status.get("timestamp")
}
except (json.JSONDecodeError, OSError):
Expand Down Expand Up @@ -1725,7 +1743,7 @@ def interview_agent(

ipc_client = SimulationIPCClient(sim_dir)

if not ipc_client.check_env_alive():
if not cls.check_env_alive(simulation_id):
raise ValueError(f"模拟环境未运行或已关闭,无法执行Interview: {simulation_id}")

logger.info(f"发送Interview命令: simulation_id={simulation_id}, agent_id={agent_id}, platform={platform}")
Expand Down Expand Up @@ -1787,7 +1805,7 @@ def interview_agents_batch(

ipc_client = SimulationIPCClient(sim_dir)

if not ipc_client.check_env_alive():
if not cls.check_env_alive(simulation_id):
raise ValueError(f"模拟环境未运行或已关闭,无法执行Interview: {simulation_id}")

logger.info(f"发送批量Interview命令: simulation_id={simulation_id}, count={len(interviews)}, platform={platform}")
Expand Down Expand Up @@ -1897,7 +1915,7 @@ def close_simulation_env(

ipc_client = SimulationIPCClient(sim_dir)

if not ipc_client.check_env_alive():
if not cls.check_env_alive(simulation_id):
return {
"success": True,
"message": "环境已经关闭"
Expand Down
3 changes: 2 additions & 1 deletion backend/app/utils/llm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def __init__(

self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
base_url=self.base_url,
default_headers={"User-Agent": "python-requests/2.32.5"}
)

def _create_completion(
Expand Down
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies = [
# 工具库
"python-dotenv>=1.0.0",
"pydantic>=2.0.0",
"psutil>=5.9.0",
]

[project.optional-dependencies]
Expand Down
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ python-dotenv>=1.0.0

# 数据验证
pydantic>=2.0.0
psutil>=5.9.0
Loading