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
10 changes: 5 additions & 5 deletions backend/scripts/run_parallel_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ async def _interview_single_platform(self, agent_id: int, prompt: str, platform:
env, agent_graph, actual_platform = self._get_env_and_graph(platform)

if not env or not agent_graph:
return {"platform": platform, "error": f"{platform}平台不可用"}
return {"platform": platform, "error": f"{platform} platform is unavailable"}

try:
agent = agent_graph.get_agent(agent_id)
Expand Down Expand Up @@ -373,7 +373,7 @@ async def handle_interview(self, command_id: str, agent_id: int, prompt: str, pl

# 未指定平台:同时采访两个平台
if not self.twitter_env and not self.reddit_env:
self.send_response(command_id, "failed", error="没有可用的模拟环境")
self.send_response(command_id, "failed", error="No simulation environment is available")
return False

results = {
Expand Down Expand Up @@ -408,7 +408,7 @@ async def handle_interview(self, command_id: str, agent_id: int, prompt: str, pl
print(f" Interview完成: agent_id={agent_id}, 成功平台数={success_count}/{len(platforms_to_interview)}")
return True
else:
errors = [f"{p}: {r.get('error', '未知错误')}" for p, r in results["platforms"].items()]
errors = [f"{p}: {r.get('error', 'unknown error')}" for p, r in results["platforms"].items()]
self.send_response(command_id, "failed", error="; ".join(errors))
print(f" Interview失败: agent_id={agent_id}, 所有平台都失败")
return False
Expand Down Expand Up @@ -511,7 +511,7 @@ async def handle_batch_interview(self, command_id: str, interviews: List[Dict],
print(f" 批量Interview完成: {len(results)} 个Agent")
return True
else:
self.send_response(command_id, "failed", error="没有成功的采访")
self.send_response(command_id, "failed", error="No interviews succeeded")
return False

def _get_interview_result(self, agent_id: int, platform: str) -> Dict[str, Any]:
Expand Down Expand Up @@ -597,7 +597,7 @@ async def process_commands(self) -> bool:
return False

else:
self.send_response(command_id, "failed", error=f"未知命令类型: {command_type}")
self.send_response(command_id, "failed", error=f"Unknown command type: {command_type}")
return True


Expand Down
81 changes: 81 additions & 0 deletions backend/tests/test_parallel_simulation_error_strings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Tests for issue #29 (A3): IPC error strings must be in English.

`run_parallel_simulation.py` previously returned untranslated Chinese error
strings (e.g. "没有成功的采访") from the OASIS/IPC layer, which leaked all the
way to the public `/campaign/<id>/inject` API response. This is a standalone
script (not part of the `app` package), so it's loaded directly from its file
path — no real OASIS/LLM calls involved.
"""

import asyncio
import importlib.util
import os
import re

import pytest

SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts")
SCRIPT_PATH = os.path.join(SCRIPTS_DIR, "run_parallel_simulation.py")

# Matches any CJK ideograph.
_CJK_RE = re.compile(r"[一-鿿]")


def _load_module():
spec = importlib.util.spec_from_file_location("run_parallel_simulation", SCRIPT_PATH)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod


@pytest.fixture(scope="module")
def rps():
return _load_module()


def test_no_cjk_characters_in_ipc_error_strings():
"""Static regression guard: no `error=...` argument to send_response may
contain untranslated Chinese text (console-only print() logging is fine)."""
with open(SCRIPT_PATH, encoding="utf-8") as f:
source = f.read()

for match in re.finditer(r"error=(f?[\"'].*?[\"'])", source):
literal = match.group(1)
assert not _CJK_RE.search(literal), f"Untranslated CJK text in IPC error string: {literal}"


def test_batch_interview_failure_message_is_english(tmp_path, rps, monkeypatch):
async def scenario():
handler = rps.ParallelIPCHandler(simulation_dir=str(tmp_path))
# No env registered at all -> handle_batch_interview finds nothing to
# interview and must report the empty-results failure in English.
captured = {}
monkeypatch.setattr(
handler, "send_response",
lambda command_id, status, result=None, error=None: captured.update(
{"status": status, "error": error}
),
)

await handler.handle_batch_interview(
command_id="cmd1",
interviews=[{"agent_id": 1, "prompt": "hi", "platform": "twitter"}],
)

assert captured["status"] == "failed"
assert captured["error"] == "No interviews succeeded"
assert not _CJK_RE.search(captured["error"])

asyncio.run(scenario())


def test_interview_unavailable_platform_message_is_english(tmp_path, rps):
async def scenario():
handler = rps.ParallelIPCHandler(simulation_dir=str(tmp_path))
# No twitter env registered -> platform unavailable.
result = await handler._interview_single_platform(agent_id=1, prompt="hi", platform="twitter")
assert "error" in result
assert not _CJK_RE.search(result["error"])
assert result["error"] == "twitter platform is unavailable"

asyncio.run(scenario())
Loading