LLM 负责把任务写自然,compiler 负责把任务变成能跑的 benchmark 文件。

有了 seed 之后,下一步就是生成任务草稿。

这一层我觉得设计得很清楚:LLM 参与自然语言任务生成,最终可执行文件由规则 compiler 生成。

这就避免了一个很常见的问题:让 LLM 直接写 benchmark 代码,结果格式、字段、评测逻辑都容易抖。

1. 输入是 seed 子图

LLM 的输入来自前面采样出来的 seed。

seed 里有真实实体和关系:

{
  "seed_id": "SEED_L3_PERMISSION_001",
  "difficulty": 3,
  "motif_id": "L3_PERMISSION_APPROVAL",
  "domains": ["document", "permission"],
  "actors": [
    {
      "user_id": "user017",
      "name": "Alice",
      "role": "document_owner"
    },
    {
      "user_id": "user031",
      "name": "Bob",
      "role": "requester"
    }
  ],
  "entities": {
    "permission_request": {
      "request_id": "req000123",
      "document_id": "doc000456",
      "requester_id": "user031",
      "owner_id": "user017",
      "permission_requested": "edit",
      "status": "pending"
    }
  },
  "allowed_tools": [
    "list_permission_requests",
    "get_document",
    "approve_permission_request",
    "list_document_permissions"
  ]
}

这相当于把题目材料都准备好了。

LLM 要做的是把这个结构化材料改写成自然、多阶段、可评测的用户任务。

2. task_draft.json 的结构

LLM 输出的是 task_draft.json

这个 draft 是中间表示,后面还会继续编译成最终任务文件。

它里面通常包含:

task_meta
stages
evaluation_outline

每个 stage 里会写:

stage_id
role
user_id
user_says
stage_goal
depends_on_stages
required_tools
optional_tools
forbidden_tools
fill_blanks
state_checks
state_change

这里有两个很关键的评测结构。

第一个是 fill_blanks

它用于读任务,比如让 Agent 查出 request_id、document_id、requester_id。

第二个是 state_checks

它用于写任务,比如审批后检查 PermissionRequest 是否变成 approved,DocumentPermission 是否出现 edit 权限。

这两个结构让任务可以自动评测。

3. 多阶段任务怎么表达

以权限审批为例,task draft 可能有两个阶段。

第一阶段:

用户说:找到 Partner Ops launch checklist 对应的 pending edit request。
目标:定位 request_id、document_id、requester_id。
工具:list_permission_requests、get_document。
评测:fill_blanks 检查三个 ID。

第二阶段:

用户说:审批刚才那个请求,并确认对方获得 edit 权限。
目标:沿用第一阶段找到的 request_id。
工具:approve_permission_request、list_document_permissions。
评测:state_checks 检查数据库状态。

这就能测试 Agent 的跨阶段记忆。

如果第一阶段找到的是 req000123,第二阶段就要审批同一个 request。

任务里会有 cross-stage rules:

Stage 2 approve 的 request_id 要来自 Stage 1。
最终权限要对应同一个 document_id 和 requester_id。

这种任务比单轮查询更像真实企业工作。

4. compiler 做什么

LLM 生成 draft 之后,compiler 会把它编译成两类文件:

task.md
task.py

task.md 给人看,方便人工检查任务语义。

task.py 给 benchmark runner 和 evaluator 用,里面有可执行任务定义、数据库补丁、阶段评测要求和跨阶段检查。

编译逻辑大概是:

读取 task_draft.json
读取 seed.json
补全 expected 字段
根据 seed 构造 db_setup patches
校验 stage / fill_blanks / state_checks
生成 cross-stage checks
render task.md
render task.py
写入 benchmark/tasks

这里我觉得最好的设计是:LLM 输出 JSON,compiler 确定性生成最终任务。

自然语言表达交给 LLM。

可执行结构交给规则代码。

5. 为什么要多一个 draft 层

很多项目会直接让 LLM 写最终代码。

但 benchmark 任务文件对格式要求很高,字段错一个就跑崩。

EntBench-Agent 多加一层 task_draft.json,就有了缓冲区。

LLM 的输出先落到结构化 JSON。

规则门控可以检查 JSON。

compiler 可以修补一些可推断字段。

最终 .py 文件保持稳定。

这样做的好处很明显:

任务自然性来自 LLM
实体真实性来自 seed
可执行性来自 compiler
评测稳定性来自 state_checks

这就是这一层最重要的工程思路。