From 4db9227e9eeb482e51c27945bf8dda1cc1116492 Mon Sep 17 00:00:00 2001 From: PRATHAMESH75 Date: Wed, 1 Jul 2026 22:40:10 +0530 Subject: [PATCH] fix(simulation): translate untranslated Chinese IPC error strings (#29 A3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Injection failures were surfacing raw Chinese error text (e.g. "没有成功的采访") through the OASIS/IPC layer all the way to the public /campaign//inject API response — not acceptable for an English public launch. Translates the IPC-facing error strings in run_parallel_simulation.py (platform unavailable, no simulation environment available, no interviews succeeded, unknown command type). Internal console/log-only print() statements are left as-is. --- backend/scripts/run_parallel_simulation.py | 10 +-- .../test_parallel_simulation_error_strings.py | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 backend/tests/test_parallel_simulation_error_strings.py diff --git a/backend/scripts/run_parallel_simulation.py b/backend/scripts/run_parallel_simulation.py index 2a627ff..c7952c0 100644 --- a/backend/scripts/run_parallel_simulation.py +++ b/backend/scripts/run_parallel_simulation.py @@ -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) @@ -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 = { @@ -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 @@ -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]: @@ -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 diff --git a/backend/tests/test_parallel_simulation_error_strings.py b/backend/tests/test_parallel_simulation_error_strings.py new file mode 100644 index 0000000..451435b --- /dev/null +++ b/backend/tests/test_parallel_simulation_error_strings.py @@ -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//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())