lili 675a8e688b feat(cheap-worker): 便宜档 Node→Python 重写进 AgentScope spike — 范式可移植坐实
WU-A 迁移面 spike:便宜档生成核心(多轮 ReAct + 五工具沙箱 + done 门)用 Python 在新建 cheap-worker/
重写成 AgentScope agent,import 复用 tier2 框架层(model client/四道熔断/历史压缩/observability),
check/build/九门 shell-out 现有 node/cjs(不重写 esbuild/CDP/形状门)。

- U1 地基:跨包 sys.path import tier2 worker + 代理旁路 + key 从内网文档注入 env + M3 装配
- U2 五工具:read/list/write Python 实现 + check/build shell-out(形状门三态与 Node 一致)
- U3 LittleJS system prompt:prompt.mjs Python 化(两条 footgun 正反例 + 输入契约)
- U4 scaffold/stage/play 薄壳 + ensure_play_spec(据 state 形态产 play-spec 治链路假绿)
- U5 ReAct 主编排:内置 Agent+ReActConfig+外层有界 resume + done 门 + 收口 stage/smoke/九门
- U6 对照 Node:产物形态 + 九门逐门 + 过门率对比

验收:同 brief Python 新路 vs Node 旧路,产物都真 src/ 6 文件、九门都 9/9 全 PASS、过门率 1.0=1.0
→ spikePass=True。端到端单局 attempt 1 一次收敛、148s。落点 cheap-worker/ 对 tier2/amodel-gen
零改动(结构性零回归)。测试 38/38 全绿。

origin: docs/plans/2026-06-26-001-feat-cheap-worker-python-spike-plan.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 02:42:14 -07:00

83 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
test_compare.py — U6 对比逻辑单测(mock verdict + 产物形态识别,不依赖真跑)。
跑:cheap-worker/.venv/bin/python cheap-worker/tests/test_compare.py
"""
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/
import compare_node as C
def test_gates_from_verdict():
v = {"pass": True, "guards": {"A_boot": {"pass": True}, "H_progress": {"pass": False}}}
assert C.gates_from_verdict(v) == {"A_boot": True, "H_progress": False}
assert C.gates_from_verdict(None) == {}
def test_compare_equivalent():
"""两路九门全过 + Python 真 src/ → spike 成功。"""
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": True}, py_real_src=True)
assert r["spikePass"] is True
assert r["pyPassRate"] == 1.0 and r["nodePassRate"] == 1.0
def test_compare_py_better():
"""Python 过门率高于 Node → spike 成功,delta>0。"""
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": False}, py_real_src=True)
assert r["spikePass"] is True and r["rateDelta"] > 0
def test_compare_py_worse():
"""Python 过门率低于 Node → 未达标,delta<0。"""
r = C.compare_logic({"A": True, "B": False}, {"A": True, "B": True}, py_real_src=True)
assert r["spikePass"] is False and r["rateDelta"] < 0
def test_compare_py_not_real_src():
"""Python 非真 src/ 多文件 → 即使过门率相等也判未达标(doc↔code 兑现门)。"""
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": True}, py_real_src=False)
assert r["spikePass"] is False
def test_src_shape_real_multifile():
with tempfile.TemporaryDirectory() as d:
src = Path(d) / "src"
src.mkdir()
(src / "game-logic.js").write_text("x", encoding="utf-8")
(src / "host-config.js").write_text("y", encoding="utf-8")
s = C.src_shape(Path(d))
assert s["realSrcMultifile"] is True and s["jsCount"] == 2
def test_src_shape_single_file():
with tempfile.TemporaryDirectory() as d:
src = Path(d) / "src"
src.mkdir()
(src / "game-logic.js").write_text("x", encoding="utf-8") # 单文件 → 非"多文件"
s = C.src_shape(Path(d))
assert s["realSrcMultifile"] is False and s["jsCount"] == 1
def test_src_shape_empty():
with tempfile.TemporaryDirectory() as d:
s = C.src_shape(Path(d)) # 无 src/ → 空壳
assert s["realSrcMultifile"] is False and s["jsCount"] == 0
if __name__ == "__main__":
_fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
_failed = 0
for _fn in _fns:
try:
_fn()
print(f" PASS {_fn.__name__}")
except Exception as e: # noqa: BLE001
_failed += 1
print(f" FAIL {_fn.__name__}: {type(e).__name__}: {e}")
print(f"\n{len(_fns) - _failed}/{len(_fns)} passed")
sys.exit(1 if _failed else 0)