Alice 写代码、Bob 找 bug、混元当裁判:我让 3 个 hy3 在两个 Cube Sandbox 里互相找茬
Alice 写代码、Bob 找 bug、混元当裁判:我让 3 个 hy3 在两个 Cube Sandbox 里互相找茬
为什么要做这件事
前面第 3 篇用了 1 个 Agent + N 个沙箱做数据分析;这次我想试更刺激的东西——让 LLM 互相找茬:
- 一个 Agent 写解法(Alice,攻方)
- 另一个 Agent 看完代码后写 testcase 攻击(Bob,守方)
- 第三个 Agent 看完 3 轮交互做仲裁(Judge)
这个模式有意思的地方在:它不靠人类设标准答案——Bob 自己设计期望值。所以裁判要同时评价"Alice 的代码对不对"和"Bob 的 testcase 答案算得对不对",是一种"自洽性测试"。
更刺激的是:跑完三轮我才发现,Bob 反复"找到 bug",但每次都是 Bob 自己算错了答案。这个反转过程恰好揭示了一件事:LLM 写代码可能比 LLM 算心算更靠谱。
1. 架构:3 Agent × 2 Sandbox
3 个角色,各自独立的 OpenAImessages历史,互不可见对方的思考过程(只可见对方的产出):
| 角色 | 看得到的输入 | 产出 | 在哪里跑 |
|---|---|---|---|
| Alice(hy3-preview) | 题目 + 上轮 Bob 的 testcase + 失败明细 | Python 解法代码 | Cube Sandbox A:/workspace/solution.pyimport 自测 |
| Bob(hy3-preview) | 题目 + Alice 当前代码全文 | Python testcase 脚本 | Cube Sandbox B:/workspace/{solution,test}.py+runpy.run_path触发 |
| Judge(hy3-preview) | 题目 + 3 轮 transcript + 3 轮 PASS/FAIL | 结构化 JSON 判决 | (无沙箱,纯文本) |
题目我选了LeetCode #3 最长无重复字符子串——经典、边界条件多(空串、Unicode、emoji、超长输入),适合 Bob 设计攻击 case。
2. Round 1:Alice 出招
Alice 拿到题目后直接给出经典滑动窗口实现:
主要骨架(hy3 写的,原样):
deflongest_unique_substring(s:str)->int:char_index={}left=max_len=0forright,chinenumerate(s):ifchinchar_indexandchar_index[ch]>=left:left=char_index[ch]+1char_index[ch]=rightifright-left+1>max_len:max_len=right-left+1returnmax_len时间 O(n)、空间 O(min(n, charset))、滑动窗口配 last-seen-index 哈希表——是个对中文/emoji/控制字符都没特殊处理需求的实现(因为 Pythonstr迭代本来就是 codepoint)。
Bob 拿到这段代码后,写了 12 个 testcase。runpy.run_path('/workspace/test.py', run_name='__main__')在 Cube Sandbox B 里跑:
PASS 10/12 [FAIL] unicode chinese + emoji [FAIL] spaces and punctuation—— Bob 发现两个"问题"。Alice 没看到 Bob 的 testcase(也不需要看),只看到失败的输入和 Bob 预期的答案,然后修代码。
3. Round 2 / Round 3:每轮 Bob 都"找到新 bug"
Alice 在 R2、R3 微调了字典初始化、注释等小处(核心算法没变,因为算法是对的)。Bob 每轮都加新攻击 case:
- R2:13/15(新增
spaces and control chars和mixed case sensitive) - R3:15/18(新增
surrogate pair、控制字符 + 标点组合等)
把三轮通过率画出来,再把"Bob 找到的所有失败"逐项展开看真相:
最戏剧化的是 R3 这一轮。我把 Bob 在 Cube Sandbox B 里的真实运行 stdout 截下来:
[FAIL] control chars + punctuation input: 'a\tb\nc d,e!' expected: 8 actual: 10 [FAIL] mixed case sensitive input: 'AaAaBb' expected: 3 actual: 4 [FAIL] surrogate pair repeat input: '\ud83d\ude00\ud83d\ude00' expected: 1 actual: 2每个"失败"都看起来很专业。但我手算了一下:
| 输入 | Bob 期望 | Alice 输出 | 实际算一下 |
|---|---|---|---|
'a\tb\nc d,e!' | 8 | 10 | 10 个 char 全不重复:a,\t,b,\n,c,space,d,comma,e,! ✅ Alice 对 |
'AaAaBb' | 3 | 4 | A,a,A,a,B,b(区分大小写)→ 最长aAaB或AaBb长 4 ✅ Alice 对 |
'\ud83d\ude00\ud83d\ude00' | 1 | 2 | Pythonstr里这是 4 个 char(两个 surrogate × 2),最长前 2 个不重复,长 2 ✅ Alice 对 |
三个"失败"全部是 Bob 自己算错了期望值。Alice 的代码从 R1 到 R3 都没错过,错的是 Bob 算心算。
4. Judge 出场:混元怎么看这场对决?
裁判看完 3 轮完整 transcript(含每轮 Alice/Bob 代码 + 沙箱真实 stdout)后,返回结构化 JSON:
Judge 的核心结论(原文照抄):
"alice_score": 9, "bob_score": 8, "winner": "alice"alice_review:Alice 的代码采用滑动窗口+哈希表的标准解法,时间复杂度 O(n)、空间复杂度 O(min(n,字符集)),完全符合题目要求。代码正确性上,对空串、单字符、全重复字符、经典 ASCII 用例、Unicode 中文/emoji、大小写敏感场景均能正确处理,仅 Bob 设计的 3 个特殊用例(控制字符组合、大小写重复、surrogate pair)的预期值存在偏差,实际输出符合最长无重复子串的定义,代码本身无逻辑错误。
bob_review:Bob 的测试用例设计覆盖度较好,包含边界场景(空串、单字符、全重复)、经典用例、Unicode 中文/emoji、控制字符、空格标点、大小写敏感、surrogate pair 等多类场景,攻击力度较强,但存在 3 处预期值设计错误:控制字符组合、大小写重复、surrogate pair 用例的实际最长无重复长度与预期不符,说明对题目规则的理解存在偏差,部分用例的预期值计算不准确,影响了测试的有效性。
verdict:Alice 的代码实现完全符合题目要求,逻辑正确且性能健壮,Bob 的测试用例覆盖全面但存在部分预期值错误,整体 Alice 表现更优。
Judge独立看出来了那 3 个 case 是 Bob 算错——而且原因和我手算的一模一样。这是个挺重要的信号:LLM-as-Judge 在这种"看代码读 stdout 推断逻辑"的任务上是可靠的,前提是你把上下文(代码 + stdout)一次性塞给它,让它推理而不是检索。
5. 为什么这个故事值得讲?
我开始这个实验时的预期是:Alice 多多少少会被 Bob 抓到 1-2 个 bug,3 轮后修通。但实际情况是 Alice 的代码从 R1 到 R3 没有错过——因为 hy3-preview 写经典 LeetCode 算法的稳定性比预想好;反而是 Bob 在算 testcase 期望值时一次又一次手算翻车。
这个反转给我两条挺反直觉的工程经验:
LLM 写代码 vs LLM 算心算:写代码(生成结构)的稳定性 > 算心算(一步一步推数字)。如果你的 Agent pipeline 里两步都要做,让代码跑、再读 stdout 比让 LLM 直接给数字稳得多。这正是 Cube Sandbox 在 Agent 里的价值定位:给 LLM 一个『可以验算自己结论』的真实环境。
多 Agent 自洽性测试有意义但要小心:Alice 和 Bob 共用同一个 hy3-preview。理论上他们对题目的理解应该一致,结果在 Unicode/surrogate pair 这类细节上居然出现分歧——这正是"对抗式纠错"暴露 LLM 知识表征不稳定的地方。Judge 这一层非常关键,不能只看 PASS/FAIL,要看代码逻辑和数据本身。
而 Cube Sandbox 在这个架构里的作用很纯粹:它不参与"对错"的判断,它只负责让代码真实地跑起来。Alice 和 Bob 看到的都是真实 stdout,Judge 看到的也是真实运行结果——三方共享同一个事实底座。如果用 Docker 默认配置跑两人各自的代码会怎样?参见我上篇 [Cube Sandbox 攻击对照实测]——tcp 172.17.0.1:22 OPEN那张图,那就是另一个故事了。
6. 工程总结:可复用的 4 条经验
每个 Agent 独立
messages历史,不共享上下文:Alice 永远看不到 Bob 的思考过程(只看产出),Bob 也一样。这是模拟"黑盒攻击"的关键,否则 Bob 看到 Alice 的"我担心 emoji 边界"内心 OS,就少了一半攻击想法。每个 Agent 独立 Cube Sandbox:Alice 的代码不能污染 Bob 的运行环境(万一 Alice 的代码改了全局变量?万一 Bob 的 testcase 把
solution.py改坏了?)。Cube 65 ms 冷启动让"多沙箱"在工程上完全免费。runpy.run_path(..., run_name='__main__')触发 testcase:直接import test_module不会执行if __name__ == '__main__'块——这是我第一次跑时的坑,3 轮 stdout 全空。改用runpy.run_path后正常。Judge 一定要拿到真实 stdout,不能只拿 PASS/FAIL:如果只告诉 Judge “R3 通过率 15/18”,它没办法判断"那 3 个失败究竟是 Alice 错还是 Bob 错"。把 sandbox stdout 完整喂给 Judge,让它自己推理。
7. 接下来还能玩什么
这个对抗式 + 仲裁的范式可以扩展到很多场景:
- N 个解法竞赛:让 hy3 想 5 种不同思路(暴力 / 双指针 / DP / 贪心 / 数学),在 5 个沙箱并行跑同一组 testcase,按性能 + 内存排名
- 跨模型对抗:Alice 用混元、Bob 用 DeepSeek、Judge 用 GPT-4——是个有趣的"LLM 互测"基准
- 多语言对抗:题目同一个,Alice 写 Python、Bob 写 Rust testcase——配合我上篇文章的多语言 sandbox 用法
但这些都是后话。这一篇我想说的最核心一件事是:当你给 LLM 一个真实的代码运行环境(比如 Cube Sandbox),它从『会说』升级到『能验证自己说得对不对』,整个 Agent 体系就开始有趣了。
附录 A:完整可复现代码
scripts/debate.py~200 行。跑法:
exportOPENAI_API_KEY=sk-...# TokenHub 的 keyexportOPENAI_BASE_URL=https://tokenhub.tencentmaas.com/v1exportE2B_API_URL=http://127.0.0.1:3000exportE2B_API_KEY=dummyexportCUBE_TEMPLATE_ID=tpl-...exportSSL_CERT_FILE=/root/.local/share/mkcert/rootCA.pem python3 scripts/debate.py3 个角色的 system prompt、3 轮 transcript、Judge JSON 全部归档在transcripts/和logs/02_result.json。
附录 B:参考链接
- Cube Sandbox:https://github.com/TencentCloud/CubeSandbox
- e2b-code-interpreter SDK:https://github.com/e2b-dev/code-interpreter
- 混元 OpenAPI(OpenAI 兼容):腾讯云 TokenHub
本文 Alice、Bob、Judge 三个角色全部由腾讯混元 hy3-preview 扮演(共享同一个 TokenHub API key、独立 messages 历史);两个 Cube Sandbox 跑在 OpenCloudOS 9.4 + PVM 内核上;所有 stdout、判决 JSON、3 轮 transcript 均可独立复核。
