Skip to content

feat (AgentIF): agentic instruction-following benchmark implementation - #2210

Merged
cmunley1 merged 27 commits into
NVIDIA-NeMo:mainfrom
pachmu:mbagdasarova/agentif-integr
Aug 28, 2026
Merged

cmunley1 merged 27 commits into
NVIDIA-NeMo:mainfrom
pachmu:mbagdasarova/agentif-integr

Conversation

@pachmu

@pachmu pachmu commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

AgentIF β€” agentic instruction-following benchmark

Adds a resources server for AgentIF, ported from the THU-KEG
AgentIF benchmark (707 real-world
agentic scenarios). Each scenario carries a list of natural-language constraints
that the model's response must satisfy; constraints are scored by a pipeline of
typed checkers declared in the data.

What it does

Each constraint runs through a checker pipeline whose step types are:

Step type Mechanism
llm Calls the judge model; rewrites the working text to the judge's output
llm_conditional_check Calls the judge to test if the constraint applies; skips (β†’ None) if not
code Executes a dataset-provided check_following(response) in a sandboxed globals dict

The pipeline resolves to True / False / None (None = unscored: conditional
guard unmet, judge failure, or code error). Two headline metrics follow upstream:

Metric Definition
CSR (Constraint Success Rate) n_true / (n_true + n_false) across all scored constraints
ISR (Instruction Success Rate) fraction of rows where every scored constraint passed

Metrics

  • csr β€” corpus-level constraint success rate (primary reward signal)
  • isr β€” instruction success rate (all-or-nothing per row)
  • mean_reward β€” mean per-row CSR
  • by_dimension/{vanilla,condition,example}/accuracy β€” per-dimension breakdown
  • by_type/{formatting,semantic,tool}/accuracy β€” per-type breakdown
  • n_null_total β€” total unscored constraints (diagnostics)

Contents

  • app.py β€” verify() (constraint pipeline + CSR reward) + compute_metrics() /
    get_key_metrics(). Strips <think> blocks before scoring. Code checkers run
    in an executor; judge concurrency is bounded by asyncio.Semaphore.
  • prepare_agentif.py β€” converts the upstream THU-KEG/AgentIF eval.json into
    Gym JSONL format; strips pre-existing score keys so the verifier re-derives
    verdicts from the model's generation.
  • configs/agentif.yaml β€” simple-agent + policy + judge model wiring for
    eval/training.
  • configs/agentif_serve.yaml β€” serve-only config (resources server + judge, no
    policy) for nemo-evaluator gym://...protocol=native flow.
  • data/example.jsonl β€” committed synthetic samples covering constraint types and
    dimensions.
  • tests/test_app.py β€” checker pipeline (all step types), conditional branching,
    upstream parity (code-fail β†’ llm skipped), compute_metrics, dimension/type
    breakdowns, key-metric selection. 39 tests.

Testing

ng_test +entrypoint=resources_servers/agentif      # 39 passed
ruff check resources_servers/agentif/              # clean

Notes

  • verified: false β€” not yet baselined. Reward profiling across an
    open-instruct / open-thinking / closed-source model suite is a follow-up
    before flipping to verified: true.
  • Recommended judge: gpt-4o-mini (upstream used gpt-4o-2024-11-20). Judge
    credentials are passed at launch via +judge_base_url=... / +judge_api_key=...
    / +judge_model_name=....
  • code checkers use exec in a fresh per-call globals dict (never the process
    globals()), so concurrent rollouts cannot interfere. The AgentIF dataset is
    trusted (curated by THU-KEG); checkers only inspect the model's response string.

pachmu added 5 commits July 16, 2026 12:07
…ollowing

Ports the THU-KEG AgentIF benchmark (707 real-world agentic scenarios) to a
NeMo Gym resources server. Each row carries a list of natural-language
constraints scored by a pipeline of checkers: ``llm`` /
``llm_conditional_check`` steps call an LLM judge; ``code`` steps exec a
dataset-provided ``check_following(response)`` function in a fresh globals
dict so concurrent rollouts never interfere.

Two headline metrics follow upstream ``1.evaluation_api.py``:

- CSR (Constraint Success Rate) β€” fraction of scored constraints that passed;
  returned by ``verify()`` as the per-row reward.
- ISR (Instruction Success Rate) β€” fraction of rows where *every* scored
  constraint passed (all-or-nothing per row).

Per-row breakdowns by dimension (unconditional / conditional / example_driven)
and type (formatting / semantic / resource) are stashed so ``compute_metrics``
can reconstruct the corpus-level dimension and type accuracy tables.

- verify() strips <think> blocks before scoring (thinking-model tolerant)
- prepare_agentif.py converts the upstream THU-KEG/AgentIF eval.json into Gym
  JSONL format; strips pre-existing ``score`` keys so the verifier re-derives
  them from the model's own generation
- agentif.yaml wires policy model + judge model for full eval/training;
  agentif_serve.yaml is a serve-only config (no policy agent/model) for use
  with the nemo-evaluator gym:// adapter
- Asyncio semaphore bounds concurrent judge calls (default 32);
  code checkers run in an executor to stay non-blocking
- committed data/example.jsonl for smoke tests; unit tests cover all checker
  types, the conditional pipeline, compute_metrics, dimension/type breakdowns,
  and key-metric selection (39 tests)

Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
@pachmu pachmu changed the title AgentIF β€” agentic instruction-following benchmark implementation (feat) AgentIF β€” agentic instruction-following benchmark implementation Jul 30, 2026
@pachmu pachmu changed the title (feat) AgentIF β€” agentic instruction-following benchmark implementation feat (AgentIF): agentic instruction-following benchmark implementation Jul 30, 2026
pachmu added 3 commits July 30, 2026 14:31
… README

Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
…R definitions

Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
@github-actions github-actions Bot added the sla:triage-overdue Review assignment is over the one-business-day SLA label Jul 31, 2026
@cmunley1

Copy link
Copy Markdown
Contributor

/claude review

Comment thread resources_servers/agentif/app.py Outdated
local_vars: dict = {"response": working}
for stmt in _IMPORT_RE.findall(exec_src):
exec(stmt, shared_globals) # noqa: S102
exec(exec_src, shared_globals, local_vars) # noqa: S102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKER β€” code checkers that reference top-level helpers/constants silently score None, corrupting CSR/ISR.

exec(exec_src, shared_globals, local_vars) with two distinct dicts uses Python's module-vs-class-body semantics: every top-level def/assignment binds into local_vars, but each defined function captures shared_globals as its __globals__. So when check_following calls a sibling helper or reads a module-level constant defined in the same source, the lookup goes to shared_globals + builtins and never local_vars β†’ NameError. _run_code_check catches it, returns (None, err), and _score_constraint demotes the constraint to None (dropped from ISR/CSR).

Not hypothetical β€” data/example.jsonl row 2 defines top-level helpers (get_company_info, get_sub_company_info, get_company_register, …) called from within the checker. Those constraints all silently drop out of scoring. The separate import-only loop above rescues import statements but not helper defs or constants, so the workaround is incomplete.

BLAST RADIUS: every AgentIF row whose code checker uses a helper or module-level constant scores None instead of True/False. CSR/ISR are computed only over scored constraints, so reported numbers are silently biased toward trivial single-function checkers β€” wrong eval numbers in model reports, wrong reward signal if used for training.

FIX: exec into a single namespace so top-level names and function __globals__ coincide:

ns: dict = {"response": working}
exec(exec_src, ns)                       # one dict β†’ helpers/constants visible to check_following
if "check_following" not in ns:
    return None, "check_following not defined"
return ns["check_following"](working), None

This also makes the separate _IMPORT_RE pre-exec loop unnecessary. Add a test with a helper-referencing checker β€” current tests only cover self-contained check_following bodies, which is why this passed CI.

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

HOLD β€” one correctness BLOCKER in the code-checker exec path; everything else is solid.

BLOCKER (inline on resources_servers/agentif/app.py:146)

_run_code_check runs exec(exec_src, shared_globals, local_vars) with two distinct dicts. Under Python's module-vs-class-body semantics, top-level defs/constants bind into local_vars while each defined function captures shared_globals as __globals__ β€” so any check_following that calls a sibling helper or reads a module-level constant raises NameError, gets caught, and the constraint silently scores None (dropped from CSR/ISR). data/example.jsonl row 2 already contains such checkers (get_company_info, get_sub_company_info, …). Result: silently biased CSR/ISR in eval reports / reward signal. Fix: exec into a single namespace (exec(exec_src, ns) with ns = {"response": working}), which also removes the need for the import-only pre-exec loop. Add a helper-referencing checker test β€” current tests only exercise self-contained bodies, so this slipped past CI.

Reviewed and clear

  • Async correctness: judge calls go through server_client.post (aiohttp) with an asyncio.Semaphore; code checkers run via run_in_executor off the event loop; no httpx, no ray.get(), no missing awaits. Good.
  • Judge failure handling: _call_judge swallows exceptions β†’ None β†’ constraint scored None, no hang/crash. Reasonable.
  • Metrics contract: compute_metrics/get_key_metrics signatures match the mixin; reward = ISR per-row, CSR corpus-wide β€” internally consistent with the tests.
  • Config conventions: null judge_* defaults at file end, verified: false, serve-vs-agent split documented; TypedDict defaults not abused.
  • Ancillary changes (ragtruth trailing-space commit-hash fix, rolemrc license CCβ†’MIT) are low-risk metadata edits.

Once the exec namespace fix lands with a regression test, this is a ship.

working = judge_text
elif etype == "code":
loop = asyncio.get_running_loop()
working, err = await loop.run_in_executor(None, _run_code_check, ev.get("exec", ""), working or "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be better to use a sandbox if possible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sandboxing check_following β€” it's curated upstream data (THU-KEG), not untrusted input, same trust level as the benchmark's own code. Happy to revisit if we ever take community-submitted checkers.

@NVIDIA-NeMo NVIDIA-NeMo deleted a comment from claude Bot Aug 17, 2026
@github-actions github-actions Bot removed the sla:triage-overdue Review assignment is over the one-business-day SLA label Aug 17, 2026
@pachmu
pachmu requested a review from cmunley1 August 20, 2026 12:04
cmunley1
cmunley1 previously approved these changes Aug 20, 2026

@cmunley1 cmunley1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some minor comments on verifier robustness, good to merge if you want

Comment thread resources_servers/agentif/app.py Outdated

if score is _UNSET:
if isinstance(working, str):
if "YES" in working:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check seems a little weak, if it contains both words it might be misclassified? e.g. NO, ... not YES receives reward 1.0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added _parse_yes_no() function to address this issue

Comment thread resources_servers/agentif/app.py Outdated

for constraint in constraints:
score = await self._score_constraint(constraint, response)
if score is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should judge/verifier failures count as evaluation failures rather than being dropped as null? one passing constraint plus one verifier exception can return reward 1.0.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, errors now count as evaluation failures for the row: isr_counted covers scored-or-errored constraints and isr_pass requires n_error == 0.

pachmu added 2 commits August 28, 2026 10:10
  Match YES/NO on word boundaries with a last-line-first scan; output mentioning both labels is ambiguous, not a pass. Split unscored constraints into skipped (conditional gate said NO β€” still excluded) and error (judge/checker failure or unusable verdict), and count errors as row-level evaluation failures so pass + exception no longer scores ISR 1.0.

Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
@pachmu
pachmu requested a review from cmunley1 August 28, 2026 11:10
@pachmu

pachmu commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

@cmunley1 the comments has been addressed

@cmunley1

Copy link
Copy Markdown
Contributor

/ok to test 0f8f4fa

@cmunley1
cmunley1 merged commit 4758612 into NVIDIA-NeMo:main Aug 28, 2026
31 checks passed

This branch was previously deployed

1 inactive deployment
public β€” 0f8f4fac Deployed Aug 28, 2026 by copy-pr-bot[bot] via release / finalize / notify #2243
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants