eval_core/lib.rs
1//! `eval-core` — **pytest/jest, but for LLM agents**: a batteries-included agent testing framework
2//! where a test case is a *prompt* and the assertions are built-in checks on what the agent DID —
3//! which tools it called, with which parameters, and what it finally said or computed — scored over
4//! the universal [`RunArtifacts`].
5//!
6//! Prompts in, assertions on behavior out; bring your own harness. For the common case a host
7//! implements ONE method ([`Agent::run`]), authors [`expect::Expectation`] predicates (in RON or
8//! inline), and calls [`run_suite`] — no `World`, no `Setup`, no [`Scorer`] impl. It is
9//! game-agnostic, so it doubles as a generic result/metric data model plus a self-contained HTML
10//! comparison report (a small "Weights & Biases for evals").
11//!
12//! ## Quickstart
13//!
14//! Implement [`Agent::run`] over your harness (run one prompt, return what the agent did via the
15//! `with_*` builders + [`ToolCall::new`]), author [`Expectation`] cases, and call [`run_suite`]:
16//!
17//! ```
18//! use eval_core::{run_suite, Agent, EvalCase, EvalError, Expectation, RunArtifacts, ToolCall};
19//! use serde_json::json;
20//!
21//! // A toy agent (no real LLM): for an "add" prompt it emits a calculator tool call and ends with
22//! // the sum; for anything else it just greets, making no tool call.
23//! struct MyAgent;
24//! impl Agent for MyAgent {
25//! fn run(&self, instruction: &str) -> Result<RunArtifacts, EvalError> {
26//! if instruction.contains("add") {
27//! Ok(RunArtifacts::new()
28//! .with_tool_calls(vec![ToolCall::new(
29//! "calculator",
30//! json!({ "op": "add", "a": 2, "b": 2 }),
31//! )])
32//! .with_final_text("The answer is 4."))
33//! } else {
34//! Ok(RunArtifacts::new().with_final_text("Hello!"))
35//! }
36//! }
37//! }
38//!
39//! let cases: Vec<EvalCase<(), Expectation>> = vec![
40//! EvalCase {
41//! name: "adds-two-numbers".to_owned(),
42//! instruction: "please add 2 and 2".to_owned(),
43//! setup: (), // no `setup` on the easy path — it is `()`
44//! expect: vec![
45//! Expectation::CalledToolWith {
46//! tool: "calculator".to_owned(),
47//! args: json!({ "op": "add" }),
48//! },
49//! Expectation::FinalNumberEquals { value: 4.0, tolerance: 0.0 },
50//! ],
51//! },
52//! EvalCase {
53//! name: "no-tools-for-chitchat".to_owned(),
54//! instruction: "hello there".to_owned(),
55//! setup: (),
56//! expect: vec![Expectation::NoToolCalls],
57//! },
58//! ];
59//!
60//! let report = run_suite(&MyAgent, &cases);
61//! assert_eq!(report.total(), 2);
62//! assert_eq!(report.passed(), 2); // both cases pass
63//! // `println!("{report}")` prints the human-readable summary table.
64//! ```
65//!
66//! In practice cases are usually authored as RON and loaded with [`load_cases`]:
67//!
68//! ```ron
69//! (
70//! name: "adds-two-numbers",
71//! instruction: "what is 2 + 2?",
72//! expect: [
73//! CalledToolWith(tool: "calculator", args: { "op": "add" }),
74//! FinalNumberEquals(value: 4.0),
75//! ],
76//! )
77//! ```
78//!
79//! `eval-core` also ships a ready-to-run [`baseline()`] suite (arithmetic / language / tool-use, 18
80//! cases) you can hand straight to [`run_suite`], and [`baseline_files`] to dump it as a template.
81//! See `examples/calculator.rs` for a complete, dependency-free agent-framework example, and the
82//! crate `README.md` for the full assertion catalog.
83//!
84//! ## Isolation guarantee
85//!
86//! This crate depends ONLY on small third-party crates (`serde`, `serde_json`, `ron`, `regex`,
87//! `thiserror`, `anyhow`, `tracing`, and `include_dir` to embed the shipped baseline suite). It has ZERO
88//! dependency on any host engine/game crate, so it can be lifted into a standalone public repository
89//! unchanged. The dependency arrow points one way: a host harness depends on `eval-core`, never the
90//! reverse.
91//!
92//! ## Modules
93//!
94//! - [`report`] — the result/metric data model: [`report::RunRecord`], [`report::EvalReport`],
95//! [`report::CaseOutcome`], with a readable `Display` summary and the aggregate statistics
96//! (accuracy, latency percentiles, token totals).
97//! - [`report_html`] — the self-contained HTML report generator ([`report_html::generate_report`]):
98//! loads persisted [`report::RunRecord`]s from a directory and writes a single offline `report.html`.
99//! - [`case`] — the generic, RON-authored case container [`EvalCase`] + the fail-loud [`load_cases`]
100//! loader (and [`parse_cases_from_str`] for one-or-many cases per file), both generic over the host's
101//! `Setup`/`Expect` types.
102//! - [`baseline`](mod@baseline) — a shipped, ready-to-run baseline capability suite
103//! ([`baseline()`](baseline()) / [`baseline_files`]): basic arithmetic / language / tool-use checks,
104//! embedded into the crate, that a user runs against their agent in one call or copies as a template.
105//! - [`harness`] — the [`Harness`] trait (the thing being benchmarked), the easy-path [`Agent`] trait,
106//! [`RunArtifacts`] (what one run produced, minus scoring), and the structured [`harness::ToolCall`].
107//! - [`expect`] — the built-in assertion library [`expect::Expectation`] (tool use / text / math /
108//! health checks over [`RunArtifacts`]), serde/RON-authored.
109//! - [`scorer`] — the [`Scorer`] trait (score one expectation against the post-run world + artifacts)
110//! and the batteries-included [`BuiltinScorer`].
111//! - [`runner`] — the generic engine: [`run_eval`] / [`run_eval_with_meta`] tie a [`Harness`] + a
112//! [`Scorer`] over a shared world; [`run_suite`] / [`run_suite_with_meta`] are the easy path
113//! ([`Agent`] + [`BuiltinScorer`]). Both run every case, time each, isolate panics, and assemble an
114//! [`report::EvalReport`].
115//! - [`error`] — the public [`EvalError`] surfaced by [`load_cases`] and [`Agent::run`].
116//!
117//! ## Advanced — the full path (custom world)
118//!
119//! When scoring needs post-run WORLD state, implement [`Harness`] over your agent + world, implement
120//! [`Scorer`] over the same world, and call [`run_eval`]. See `examples/minimal.rs`.
121
122pub mod baseline;
123pub mod case;
124pub mod error;
125pub mod expect;
126pub mod harness;
127pub mod report;
128pub mod report_html;
129pub mod runner;
130pub mod scorer;
131
132pub use baseline::{baseline, baseline_files};
133pub use case::{EvalCase, load_cases, parse_cases_from_str};
134pub use error::EvalError;
135pub use expect::Expectation;
136pub use harness::{Agent, Harness, RunArtifacts, ToolCall};
137pub use runner::{
138 AgentHarness, RunMeta, run_eval, run_eval_with_meta, run_suite, run_suite_with_meta,
139};
140pub use scorer::{BuiltinScorer, Scorer};