Repository navigation
feat (AgentIF): agentic instruction-following benchmark implementation - #2210
Conversation
β¦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>
Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
β¦ README Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
β¦R definitions Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
β¦integr # Conflicts: # README.md
Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
|
/claude review |
| 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 |
There was a problem hiding this comment.
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), NoneThis 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.
|
HOLD β one correctness BLOCKER in the code-checker exec path; everything else is solid. BLOCKER (inline on
|
| 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 "") |
There was a problem hiding this comment.
would be better to use a sandbox if possible
There was a problem hiding this comment.
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.
β¦function visibility Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
β¦ or local file. Add readme file. Signed-off-by: mbagdasarova <mbagdasarova@nvidia.com>
cmunley1
left a comment
There was a problem hiding this comment.
some minor comments on verifier robustness, good to merge if you want
|
|
||
| if score is _UNSET: | ||
| if isinstance(working, str): | ||
| if "YES" in working: |
There was a problem hiding this comment.
This check seems a little weak, if it contains both words it might be misclassified? e.g. NO, ... not YES receives reward 1.0
There was a problem hiding this comment.
added _parse_yes_no() function to address this issue
|
|
||
| for constraint in constraints: | ||
| score = await self._score_constraint(constraint, response) | ||
| if score is None: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed, errors now count as evaluation failures for the row: isr_counted covers scored-or-errored constraints and isr_pass requires n_error == 0.
β¦integr # Conflicts: # README.md
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>
|
@cmunley1 the comments has been addressed |
|
/ok to test 0f8f4fa |
AgentIF β agentic instruction-following benchmark
Adds a resources server for AgentIF, ported from the THU-KEG
AgentIFbenchmark (707 real-worldagentic 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:
llmllm_conditional_checkNone) if notcodecheck_following(response)in a sandboxed globals dictThe pipeline resolves to
True/False/None(None = unscored: conditionalguard unmet, judge failure, or code error). Two headline metrics follow upstream:
n_true / (n_true + n_false)across all scored constraintsMetrics
csrβ corpus-level constraint success rate (primary reward signal)isrβ instruction success rate (all-or-nothing per row)mean_rewardβ mean per-row CSRby_dimension/{vanilla,condition,example}/accuracyβ per-dimension breakdownby_type/{formatting,semantic,tool}/accuracyβ per-type breakdownn_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 runin an executor; judge concurrency is bounded by
asyncio.Semaphore.prepare_agentif.pyβ converts the upstreamTHU-KEG/AgentIFeval.jsonintoGym JSONL format; strips pre-existing
scorekeys so the verifier re-derivesverdicts from the model's generation.
configs/agentif.yamlβ simple-agent + policy + judge model wiring foreval/training.
configs/agentif_serve.yamlβ serve-only config (resources server + judge, nopolicy) for nemo-evaluator
gym://...protocol=nativeflow.data/example.jsonlβ committed synthetic samples covering constraint types anddimensions.
tests/test_app.pyβ checker pipeline (all step types), conditional branching,upstream parity (code-fail β llm skipped),
compute_metrics, dimension/typebreakdowns, key-metric selection. 39 tests.
Testing
Notes
verified: falseβ not yet baselined. Reward profiling across anopen-instruct / open-thinking / closed-source model suite is a follow-up
before flipping to
verified: true.gpt-4o-mini(upstream usedgpt-4o-2024-11-20). Judgecredentials are passed at launch via
+judge_base_url=.../+judge_api_key=.../
+judge_model_name=....codecheckers useexecin a fresh per-call globals dict (never the processglobals()), so concurrent rollouts cannot interfere. The AgentIF dataset istrusted (curated by THU-KEG); checkers only inspect the model's response string.