Evaluation¶
ChemGraph's primary evaluation pipeline uses the deterministic structured-output
judge. It asks each agent for a ResponseFormatter result, compares that result
field-by-field with structured ground truth, and produces reproducible binary
scores without a second judge model.
View published results on the ChemGraph Leaderboard.
The LLM-as-judge path remains available for qualitative or legacy comparisons, but it is secondary because it adds cost, nondeterminism, and judge-model bias.
No default benchmark dataset is bundled. Supply --dataset or define dataset
in a selected TOML evaluation profile.
Main pipeline¶
Ground-truth structured_output
↓
Agent ResponseFormatter JSON
↓
Deterministic field comparators
↓
Per-query score: all expected fields pass = 1, otherwise 0
↓
Accuracy and per-field diagnostics
For every model, workflow, and query, the runner:
- starts ChemGraph with structured output enabled;
- captures the agent's tool calls, final result, and
ResponseFormatterJSON; - compares each non-null expected field with the matching actual field;
- records a binary score, per-field pass/fail values, and a rationale;
- aggregates correct queries into structured-output accuracy.
Formatter/JSON parse failures score as incorrect and remain in the accuracy denominator. Ground truth with no non-null structured fields is also invalid and scores as a failure instead of receiving a trivial pass.
Quickstart¶
Run the structured judge explicitly. A judge model is not needed:
chemgraph eval \
--models gpt-4o-mini \
--dataset evaluation/questions.json \
--workflows single_agent \
--judge-type structured \
--output-dir eval_results
Always include --judge-type structured unless a selected profile already sets
it. The CLI's compatibility fallback is still llm when no judge type is
specified.
Structured ground truth¶
The preferred dataset format is a JSON array. Each item needs a unique ID,
query, and answer.structured_output object:
[
{
"id": "water-smiles",
"category": "lookup",
"query": "What is the SMILES string for water?",
"answer": {
"tool_calls": [
{
"name": "molecule_name_to_smiles",
"args": {"name": "water"}
}
],
"result": "O",
"structured_output": {
"smiles": ["O"]
}
}
}
]
Only non-null fields in the expected structured_output are judged, so include
every result modality that the query is required to return.
| Structured field | Expected shape | Comparison |
|---|---|---|
smiles |
List of SMILES strings | RDKit canonicalization; expected values matched independently of order |
scalar_answer |
{value, property, unit} |
Value within 5% relative tolerance; normalized property category; unit match |
dipole |
{value: [dx, dy, dz], unit} |
Components within 5% relative tolerance; normalized unit match |
vibrational_answer |
{frequency_cm1: [...]} |
Imaginary modes removed; sorted real modes compared within 5% |
ir_spectrum |
{frequency_cm1: [...], intensity: [...]} |
Frequencies and intensities compared within 5% |
atoms_data |
{numbers: [...], positions: [...]} |
Atomic numbers exact; each coordinate within 0.1 Å |
The default numeric tolerances come from judge_structured_output(). The CLI
runner currently uses those defaults. The loader also accepts the legacy object
format containing manual_workflow or llm_workflow, but new datasets should
use the list format above.
Keep ground truth under version control when licensing permits, document how it was produced, and review it independently of every model being measured.
Profiles¶
Make structured judging the explicit default in a reusable profile:
[eval]
default_profile = "standard"
[eval.profiles.standard]
dataset = "evaluation/questions.json"
workflow_types = ["single_agent"]
judge_type = "structured"
structured_output = true
recursion_limit = 50
max_queries = 0
Then run:
--profile requires --config. CLI values override matching profile values.
If [eval] default_profile exists, providing --config without --profile
selects it automatically.
Reports and diagnostics¶
Select --report json, markdown, console, or all (the default). Structured
results include:
structured_judge_aggregate: query count, correct count, accuracy, and parse errors for each model/workflow pair;structured_judge_details: per-query score,field_scores, rationale, query metadata, and parse error;- raw tool calls and final agent results for debugging failures.
Use the per-field rationale to distinguish a wrong scientific value from a missing field, unit/property mismatch, geometry difference, or formatter parse failure.
Resume interrupted runs¶
The runner writes per-query checkpoints even when resume is disabled. Continue with the same dataset, model/workflow configuration, and output directory:
chemgraph eval \
--models gpt-4o-mini \
--dataset evaluation/questions.json \
--judge-type structured \
--output-dir eval_results \
--resume
Use --max-queries for a smoke test, --recursion-limit to bound graph
execution, and --tags for run metadata. Evaluation currently accepts
single_agent and multi_agent workflows.
Python API¶
import asyncio
from chemgraph.eval import BenchmarkConfig, ModelBenchmarkRunner
config = BenchmarkConfig(
models=["gpt-4o-mini"],
dataset="evaluation/questions.json",
workflow_types=["single_agent"],
structured_output=True,
judge_type="structured",
output_dir="eval_results",
)
runner = ModelBenchmarkRunner(config)
asyncio.run(runner.run_all())
runner.report(format="all")
Optional LLM judge¶
Use the LLM judge only when a deterministic structured comparison does not capture the evaluation goal or when reproducing a legacy benchmark:
chemgraph eval \
--models gpt-4o-mini \
--dataset evaluation/questions.json \
--judge-type llm \
--judge-model gpt-4o \
--output-dir eval_results
Use --judge-type both to record deterministic and LLM-judge results side by
side. Report the judge model and settings, and do not substitute the LLM score
for the structured score on leaderboard-style comparisons.
Evaluation runs invoke real models and tools unless your test setup replaces them. Estimate cost, isolate credentials, and begin with a small dataset.