feat(soak): AppWorld 场景适配器——代码围栏解释器与容器执行器

复刻 dissect 的动作协议当压测负载:两条正则、first_only 策略、五条纠错说明逐字照抄,
提示词模板连同出处一起收进 tools/soak/prompts/。有两处刻意不同,都写在代码注释里——
未闭合围栏那一支不补回三反引号(公共契约要求回填历史的文本不长于模型原文),空围栏
那一支不截断。

执行器在 execute 内部顺带问一次 is_done 填 env_reported_completion,并把环境自己数的
执行次数透出来。那个数字是故障注入的判据来源:验「声明绝不重放的动作没有被执行两次」
必须数环境侧,数库自己的计数器等于我们和我们自己对账。

端到端真跑过一道题(82e2fac_1):8 步 task_completed,环境判分通过,事件数与步数与
环境执行次数三者相等,耗时 30 秒。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:16:31 -04:00
parent 4cbdb056b6
commit 21a9a30ee9
8 changed files with 1625 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
# 这两个提示词文件是什么
`run_prefix.txt``item_suffix.txt` 合起来是 AppWorld 官方 ReAct baseline 的提示词,
压测的 AppWorld 场景直接用它们。
原件是 `StonyBrookNLP/appworld` 仓库的
`experiments/prompts/react_code_agent/instructions.txt`371 行,Apache License 2.0)。
这里的两个文件经由 `reference/dissect/config/prompts/appworld/` 转手而来——那边把原件拆成
两段,并把示例演示里的主管信息换成了固定假值(`Sam Carter / sam.carter@example.com /
555-0100`)。除拆分与那处替换之外没有其他改动。原始许可与著作权归 AppWorld 作者,
Apache-2.0 允许再分发。
## 为什么照抄而不自己写一份
压测要拿 dissect 已有的 937 条真实轨迹当对照组——步数分布、停止原因构成、成功率。
提示词换一份,这三样都会跟着变,对照就失去意义:分不清是内核的行为变了还是提示词变了。
## 改动它们的后果
`observation_template` 与这份提示词是绑死的。提示词里整段示例演示都按
`Output:\n```\n…\n```\n\n` 的形态写观察,模板改了就会让示例与实况对不上,而那正是这份
提示词最想避免的事。模板的取值在 `tools/soak/scenarios/appworld.py`,改任何一边都要同时
改另一边,并且作废与 dissect 基线的对照。
## 变量
`run_prefix.txt` 里只有一个 `{{ app_descriptions }}`,值从环境跑一次
`print(apis.api_docs.show_app_descriptions())` 拿到,整个压测复用同一份。
`item_suffix.txt` 里有 `{{ main_user.first_name }}``{{ main_user.last_name }}`
`{{ main_user.email }}``{{ main_user.phone_number }}``{{ instruction }}`
前四个来自题目的主管信息,最后一个是题面。
渲染器是 `tools/soak/scenarios/appworld.py` 里自己写的极简替换,不是 Jinja——这两个模板
只用到 `{{ 名字 }}``{{ 名字.属性 }}` 两种形态,为它装一个模板引擎不值得。
渲染完会校验文本里不再残留 `{{`:留一个没替换的占位符,模型会看见字面量
`{{ instruction }}`,而那在轨迹里看起来只是模型表现差。