zapier / zapier/AutomationBench
Mapping AutomationBench's Task/AssertionRegistry/partial_credit onto EvalPort's open TestCase/Grader/Result interchange
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 290
- Forks
- 45
- PR merge metrics
- No merged PRs in 30d
Description
Hi — I maintain EvalPort, an open (Apache 2.0) interchange spec for portable LLM eval test cases, graders, and results, with TestCase / Grader / Result / ResultSet as the core objects. I spent a while in automationbench/ before writing this, so this isn't a drive-by — happy to be told it's not a fit.
What I actually read
automationbench/schema/salesforce/task.py— theTask(SalesforceRecord)pydantic model (subject,status,priority,due_date,contact_id/related_to_id/assigned_to_id,is_closed, plus thewho_id/what_idSalesforce-alias properties andto_display_dict()), andSalesforceRecordinschema/salesforce/base.py(id,created_date,last_modified_date,owner_id).automationbench/schema/world.py—WorldState, the root object composing 40+ per-app states (salesforce,hubspot,gmail,slack,quickbooks,xero,wave,zendesk, …) plusWorldMeta(schema_version,current_time,allowed_services).automationbench/rubric/registry.py—AssertionRegistry.register(assertion_type)/.check(world, assertion), thenegative_assertionmarker decorator, andAUTOMATIONBENCH_STRICT_ASSERTIONSstrict/non-strict error handling.automationbench/rubric/__init__.py— the actualpartial_credit()/task_completed_correctly()scoring functions, including the "free assertion" exclusion logic (an assertion already true in the initial state earns no credit but still penalizes regression) and theexcluded/scored: falseescape hatches for inverse tasks.automationbench/task_contract.py—task_contract_payload()/task_contract_sha256(), which fingerprint(example_id, prompt, assertions_multiset, initial_state, zapier_tools)for rollout caching.- The README's scoring section (
partial_credit0.0–1.0 as the dense reward signal,task_completed_correctly0.0/1.0 as the strict official pass rate) and the 600-task public benchmark across sales/marketing/operations/support/finance/HR.
That's a real, actively maintained benchmark (commits into early August), not a toy — so this seemed worth raising rather than skipping.
Why I think it maps cleanly
| AutomationBench | EvalPort |
|---|---|
A task's trigger prompt + info["initial_state"] (serialized WorldState) + info["zapier_tools"] |
TestCase.input + TestCase.metadata (automationbench.initial_state, automationbench.zapier_tools, automationbench.task_name) |
One assertion dict, e.g. {"type": "contact_phone_equals", "contact_id": "...", "phone": "..."}, dispatched through AssertionRegistry |
An EvalPort Grader — most naturally type: "custom" (per the spec's "Custom grader handling" rule), with the real registered assertion_type string preserved under metadata rather than forced into one of the built-in types |
partial_credit (fraction of assertions passed) |
GraderResult.score |
task_completed_correctly (strict, only 1.0 if every assertion passes) |
Result.passed |
task_contract_sha256() (hash of prompt + assertions + initial_state + tools) |
Directly relevant to the open ResultSet/repeated-attempt and suite-signing questions already being discussed in Discussion #22 |
The part I'd actually flag as non-trivial, not glossed over: partial_credit's free-assertion exclusion and negative_assertion handling are scoring semantics, not just a value — a naive to_openeval() that only copies passed/score per assertion would silently lose "this assertion was excluded because it was already true at t=0" and "this is a negative/anti-shotgun assertion that only counts if all positives passed." That needs to survive round-trip in GraderResult.metadata, or a from_openeval() back to AutomationBench's own scorer would compute a different number.
Sketch (illustrative, not a working PR yet)
def to_openeval(task_dict: dict, *, suite_id: str) -> dict:
"""AutomationBench task -> EvalPort TestCase."""
info = task_dict["info"]
return {
"id": info["task_name"],
"input": task_dict["prompt"], # list[{"role", "content"}]
"graders": [a["type"] for a in info["assertions"]], # ids only; full specs below
"metadata": {
"automationbench.initial_state": info["initial_state"],
"automationbench.zapier_tools": info["zapier_tools"],
"automationbench.domain": info.get("domain"),
},
}
def assertion_to_grader(assertion: dict) -> dict:
"""One AssertionRegistry-dispatched assertion -> EvalPort Grader."""
atype = assertion["type"]
return {
"id": atype,
"type": "custom",
"metadata": {
"automationbench.assertion_type": atype,
"automationbench.negative": atype in NEGATIVE_ASSERTION_TYPES, # from AssertionRegistry.is_negative()
"automationbench.params": {k: v for k, v in assertion.items() if k != "type"},
},
}
def result_to_openeval(state: dict, *, task_id: str) -> dict:
"""AutomationBench per-task eval state -> EvalPort Result."""
return {
"test_case_id": task_id,
"passed": bool(state["partial_credit"] == 1.0), # task_completed_correctly
"grader_results": [
{
"grader_id": ar["type"],
"score": 1.0 if ar["passed"] else 0.0,
"passed": ar["passed"],
"metadata": {"automationbench.excluded": ar["excluded"]},
}
for ar in state["_assertion_results"] # populated by rubric.partial_credit()
],
}
Comparable adapters already merged
Two existing adapters in adapters/ are the closest precedent, for different reasons:
financebench-openeval-adapter— also a real benchmark rather than a live SDK, and its README documents exactly the kind of "field that doesn't round-trip cleanly" issue I'm flagging above for the free/negative assertion semantics.braintrust-openeval-adapter— closer in spirit for the scored-agent-rollout shape (a task, a set of checks, a pass/fail + partial score per run).
Ask
Would a PR adding an automationbench-openeval-adapter (same shape as the others: to_openeval() / from_openeval() for tasks, a separate result-side converter, tests run against a real subset of the public 600-task set, README) be something this repo would want linked from, or is AutomationBench deliberately scoped to stay Zapier/verifiers-only? I'd rather ask than send an unwanted PR. If useful, I can build it against the public task set already in this repo (no need to touch the private held-out set).
— Sahi, independent contributor (not affiliated with Zapier)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by comparing the existing financebench-openeval-adapter and braintrust-openeval-adapter, then read automationbench/rubric/registry.py, automationbench/rubric/init.py, and automationbench/task_contract.py. First confirm whether this adapter belongs in EvalPort or AutomationBench; done should include task and result converters, tests using public tasks, README coverage, and preservation of free-assertion and negative-assertion semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- ai, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100