{"id":22998,"date":"2011-05-03T18:24:18","date_gmt":"2011-05-03T18:24:18","guid":{"rendered":"https:\/\/scannn.com\/prime-radiant-inc-smevals-a-framework-for-running-evals-against-small-and-large-models-%c2%b7-github\/"},"modified":"2011-05-03T18:24:18","modified_gmt":"2011-05-03T18:24:18","slug":"prime-radiant-inc-smevals-a-framework-for-running-evals-against-small-and-large-models-%c2%b7-github","status":"publish","type":"post","link":"https:\/\/scannn.com\/lv\/prime-radiant-inc-smevals-a-framework-for-running-evals-against-small-and-large-models-%c2%b7-github\/","title":{"rendered":"prime-radiant-inc\/smevals: A framework for running evals against small (and large) models \u00b7 GitHub"},"content":{"rendered":"\n<div id=\"\">\n<p dir=\"auto\"><a href=\"https:\/\/pypi.org\/project\/smevals\/\" rel=\"nofollow\"><\/a><br \/>\n<a href=\"https:\/\/github.com\/prime-radiant-inc\/smevals\/releases\"><img decoding=\"async\" src=\"https:\/\/camo.githubusercontent.com\/1c8f691e5c43f43325b1748888e233f2fd9774430e28df636a7ca3d54df8d4d3\/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f7072696d652d72616469616e742d696e632f736d6576616c733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67\" alt=\"Changelog\" data-canonical-src=\"https:\/\/img.shields.io\/github\/v\/release\/prime-radiant-inc\/smevals?include_prereleases&amp;label=changelog\" style=\"max-width: 100%;\"\/><\/a><br \/>\n<a href=\"https:\/\/github.com\/prime-radiant-inc\/smevals\/actions?query=workflow%3ATest\"><img decoding=\"async\" src=\"https:\/\/github.com\/prime-radiant-inc\/smevals\/workflows\/Test\/badge.svg\" alt=\"Tests\" style=\"max-width: 100%;\"\/><\/a><br \/>\n<a href=\"https:\/\/github.com\/prime-radiant-inc\/smevals\/blob\/main\/LICENSE\"><img decoding=\"async\" src=\"https:\/\/camo.githubusercontent.com\/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b\/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667\" alt=\"License: MIT\" data-canonical-src=\"https:\/\/img.shields.io\/badge\/License-MIT-yellow.svg\" style=\"max-width: 100%;\"\/><\/a><\/p>\n<p dir=\"auto\">A framework for running evals against small (and large) models<\/p>\n<p dir=\"auto\">Or <code>pip install smevals<\/code>, or just <code>uvx smevals --help<\/code>.<\/p>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Vocabulary used by this project<\/h2>\n<p><a id=\"user-content-vocabulary-used-by-this-project\" class=\"anchor\" aria-label=\"Permalink: Vocabulary used by this project\" href=\"#vocabulary-used-by-this-project\"><svg data-component=\"Octicon\" class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\/><\/svg><\/a><\/div>\n<p dir=\"auto\">The top-level concept is an <strong>Eval<\/strong>: a collection of Tasks used to determine how good a particular model or model-and-harness configuration is at a specific high-level capability, such as text-to-SQL, drawing a pelican riding a bicycle, or evaluating whether an implementation satisfies a provided specification.<\/p>\n<p dir=\"auto\">Evals can optionally be grouped into <strong>Suites<\/strong> of related Evals, primarily as a mechanism for organizing them on disk.<\/p>\n<p dir=\"auto\">An <strong>Eval<\/strong> is a collection of <strong>Tasks<\/strong>. These are the individual exercises that a model must complete for its abilities to be evaluated.<\/p>\n<p dir=\"auto\">A <strong>Config<\/strong> describes the setup used to attempt Tasks. It specifies a model and may include model parameters, system prompts, tools and other settings.<\/p>\n<p dir=\"auto\">To gather evidence, we create a <strong>Run<\/strong>. A Run is the immutable record of executing one Task against one Config using a <strong>Runner<\/strong>. A Runner is a reusable CLI program that may send prompts directly to a model or build on an agent harness such as Codex or Pi.<\/p>\n<p dir=\"auto\">The same Task and Config can be executed multiple times, producing multiple Runs to help account for non-deterministic results &#8211; <code>smevals run -n 5<\/code> tops each Task up to five Runs. Each Run includes a timestamp to help track these.<\/p>\n<p dir=\"auto\">A Run whose Runner exits non-zero is a <strong>failed Run<\/strong>: a harness-level error such as a network failure, not evidence about the model. Failed Runs stay on disk for debugging but are never graded, are excluded from reports, and do not count towards <code>-n<\/code> targets.<\/p>\n<p dir=\"auto\">Once we have gathered Runs, we apply a <strong>Grader<\/strong> to each Run to produce a <strong>Grade<\/strong>. A Grader is a configured sequence of <strong>Checks<\/strong>, plus rules for combining their results into that Grade.<\/p>\n<p dir=\"auto\">Checks are individual assertions or measurements. Some may be simple, such as \u201cdoes the output contain this text?\u201d Others may be more complex, such as \u201crender this SVG to an image and have an LLM judge assess it\u201d. Each Check names the <strong>Checker<\/strong> that performs it, along with configuration for that Checker such as patterns, rubrics or expected values. A Check can be marked as required, in which case its failure halts the Grader and skips the remaining Checks.<\/p>\n<p dir=\"auto\">A <strong>Checker<\/strong> is a named operation <em>or<\/em> a reusable CLI program that implements one kind of Check. <code>contains<\/code> and <code>xml-valid<\/code> are named operations, <code>..\/checkers\/render-svg<\/code> might be a custom program. The same Checker can be used by many Checks across many Graders. The Checks in a Grader execute in order and share a working directory, so a Checker may create files &#8211; such as a rendered image &#8211; which are kept with the Grade as artifacts and available to later Checks in the sequence.<\/p>\n<p dir=\"auto\">A <strong>Grade<\/strong> is the result of applying a Grader to a Run. It records the result of each Check and may contain an overall pass\/fail outcome and\/or a numeric score. Grades can also include additional notes which are not used for scoring but may help interpret the results in the future.<\/p>\n<p dir=\"auto\">We may later change the Grader we use to evaluate Runs without executing the Runs again. A single Run can therefore be evaluated multiple times, producing multiple Grades using different Graders.<\/p>\n<p dir=\"auto\">An Eval is any directory containing an <code>eval.yaml<\/code> file:<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"my-eval\/&#10;\u251c\u2500\u2500 eval.yaml            # name and description&#10;\u251c\u2500\u2500 tasks\/               # one YAML file per Task&#10;\u251c\u2500\u2500 configs\/             # one YAML file per Config&#10;\u251c\u2500\u2500 graders\/             # one YAML file per Grader&#10;\u251c\u2500\u2500 checkers\/            # custom Checker executables (by convention)&#10;\u251c\u2500\u2500 run-llm              # Runner executable (any name, any location)&#10;\u2514\u2500\u2500 runs\/                # created by smevals run - never edit by hand\">\n<pre class=\"notranslate\"><code>my-eval\/\n\u251c\u2500\u2500 eval.yaml            # name and description\n\u251c\u2500\u2500 tasks\/               # one YAML file per Task\n\u251c\u2500\u2500 configs\/             # one YAML file per Config\n\u251c\u2500\u2500 graders\/             # one YAML file per Grader\n\u251c\u2500\u2500 checkers\/            # custom Checker executables (by convention)\n\u251c\u2500\u2500 run-llm              # Runner executable (any name, any location)\n\u2514\u2500\u2500 runs\/                # created by smevals run - never edit by hand\n<\/code><\/pre>\n<\/div>\n<div class=\"markdown-heading\" dir=\"auto\">\n<h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Example Eval: Grading Haikus<\/h3>\n<p><a id=\"user-content-example-eval-grading-haikus\" class=\"anchor\" aria-label=\"Permalink: Example Eval: Grading Haikus\" href=\"#example-eval-grading-haikus\"><svg data-component=\"Octicon\" class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\/><\/svg><\/a><\/div>\n<p dir=\"auto\">Here&#8217;s how to structure a complete Eval that asks models to write haikus and grades them on their structure.<\/p>\n<p dir=\"auto\">The Eval consists of five files.<\/p>\n<p dir=\"auto\"><code>my-eval\/eval.yaml<\/code> defines a name and description:<\/p>\n<div class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"name: haiku&#10;description: &gt;-&#10;  Can the model write a haiku on demand? Graded on structure:&#10;  the reply must be exactly three lines.\">\n<pre><span class=\"pl-ent\">name<\/span>: <span class=\"pl-s\">haiku<\/span>\n<span class=\"pl-ent\">description<\/span>: <span class=\"pl-s\">&gt;-<\/span>\n<span class=\"pl-s\">  Can the model write a haiku on demand? Graded on structure:<\/span>\n<span class=\"pl-s\">  the reply must be exactly three lines.<\/span><\/pre>\n<\/div>\n<p dir=\"auto\">An Eval must have one or more Tasks. Each of these is defined as a <code>tasks\/*.yaml<\/code> YAML file.<\/p>\n<p dir=\"auto\"><code>my-eval\/tasks\/pelicans.yaml<\/code> must have a <code>name<\/code>; a <code>prompt<\/code> is the common case, but any other keys are allowed and are passed to the Runner as environment variables:<\/p>\n<div class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"name: pelicans&#10;prompt: Write a haiku about pelicans. Reply with only the haiku, three lines.\">\n<pre><span class=\"pl-ent\">name<\/span>: <span class=\"pl-s\">pelicans<\/span>\n<span class=\"pl-ent\">prompt<\/span>: <span class=\"pl-s\">Write a haiku about pelicans. Reply with only the haiku, three lines.<\/span><\/pre>\n<\/div>\n<p dir=\"auto\">An Eval also needs at least one Config, defined in <code>configs\/*.yaml<\/code>. If there is just one of these it should be called <code>default<\/code>.<\/p>\n<p dir=\"auto\"><code>my-eval\/configs\/default.yaml<\/code> &#8211; the Config named <code>default<\/code> is used when no <code>-c<\/code> option is passed to <code>smevals run<\/code>.<\/p>\n<p dir=\"auto\"><code>runner<\/code> specifies a path to an executable program relative to this file:<\/p>\n<div class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"name: default&#10;runner: ..\/run-llm&#10;model: gpt-4.1-mini\">\n<pre><span class=\"pl-ent\">name<\/span>: <span class=\"pl-s\">default<\/span>\n<span class=\"pl-ent\">runner<\/span>: <span class=\"pl-s\">..\/run-llm<\/span>\n<span class=\"pl-ent\">model<\/span>: <span class=\"pl-s\">gpt-4.1-mini<\/span><\/pre>\n<\/div>\n<p dir=\"auto\">Here&#8217;s that Runner script:<\/p>\n<p dir=\"auto\"><code>my-eval\/run-llm<\/code> &#8211; This one uses the <a href=\"https:\/\/llm.datasette.io\/\" rel=\"nofollow\">llm<\/a> CLI, but any executable honoring the contract below works. Make it executable with <code>chmod +x<\/code>:<\/p>\n<div class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"#!\/usr\/bin\/env bash&#10;set -euo pipefail&#10;&#10;llm -m &quot;$SMEVALS_MODEL&quot; &quot;$SMEVALS_PROMPT&quot;&#10;llm logs -c --json &gt; log.json\">\n<pre><span class=\"pl-c\"><span class=\"pl-c\">#!<\/span>\/usr\/bin\/env bash<\/span>\n<span class=\"pl-c1\">set<\/span> -euo pipefail\n\nllm -m <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$SMEVALS_MODEL<\/span><span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$SMEVALS_PROMPT<\/span><span class=\"pl-pds\">\"<\/span><\/span>\nllm logs -c --json <span class=\"pl-k\">&gt;<\/span> log.json<\/pre>\n<\/div>\n<p dir=\"auto\">The Eval also needs a default Grader, which will be used to grade the results of each Run:<\/p>\n<p dir=\"auto\"><code>my-eval\/graders\/default.yaml<\/code>:<\/p>\n<div class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"name: default&#10;checks:&#10;  - checker: ..\/checkers\/three-lines&#10;    required: true&#10;scoring:&#10;  pass_threshold: 1.0\">\n<pre><span class=\"pl-ent\">name<\/span>: <span class=\"pl-s\">default<\/span>\n<span class=\"pl-ent\">checks<\/span>:\n  - <span class=\"pl-ent\">checker<\/span>: <span class=\"pl-s\">..\/checkers\/three-lines<\/span>\n    <span class=\"pl-ent\">required<\/span>: <span class=\"pl-c1\">true<\/span>\n<span class=\"pl-ent\">scoring<\/span>:\n  <span class=\"pl-ent\">pass_threshold<\/span>: <span class=\"pl-c1\">1.0<\/span><\/pre>\n<\/div>\n<p dir=\"auto\">The <code>checker<\/code> can be a relative path to a script &#8211; similar to <code>runner:<\/code> above &#8211; or can be the name of a built-in checker, listed below.<\/p>\n<p dir=\"auto\"><code>my-eval\/checkers\/three-lines<\/code> &#8211; a custom Checker, also <code>chmod +x<\/code>:<\/p>\n<div class=\"highlight highlight-source-python notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"#!\/usr\/bin\/env python3&#10;import json, os, pathlib, sys&#10;&#10;raw = (pathlib.Path(os.environ[&quot;SMEVALS_RUN_DIR&quot;]) \/ &quot;output.txt&quot;).read_text()&#10;lines = [line for line in raw.strip().splitlines() if line.strip()]&#10;print(json.dumps({&#10;    &quot;score&quot;: 1.0 if len(lines) == 3 else 0.0,&#10;    &quot;metrics&quot;: {&quot;line_count&quot;: len(lines)},&#10;    &quot;notes&quot;: f&quot;{len(lines)} non-empty line(s)&quot;,&#10;}))&#10;sys.exit(0 if len(lines) == 3 else 1)\">\n<pre><span class=\"pl-c\">#!\/usr\/bin\/env python3<\/span>\n<span class=\"pl-k\">import<\/span> <span class=\"pl-s1\">json<\/span>, <span class=\"pl-s1\">os<\/span>, <span class=\"pl-s1\">pathlib<\/span>, <span class=\"pl-s1\">sys<\/span>\n\n<span class=\"pl-s1\">raw<\/span> <span class=\"pl-c1\">=<\/span> (<span class=\"pl-s1\">pathlib<\/span>.<span class=\"pl-c1\">Path<\/span>(<span class=\"pl-s1\">os<\/span>.<span class=\"pl-c1\">environ<\/span>[<span class=\"pl-s\">\"SMEVALS_RUN_DIR\"<\/span>]) <span class=\"pl-c1\">\/<\/span> <span class=\"pl-s\">\"output.txt\"<\/span>).<span class=\"pl-c1\">read_text<\/span>()\n<span class=\"pl-s1\">lines<\/span> <span class=\"pl-c1\">=<\/span> [<span class=\"pl-s1\">line<\/span> <span class=\"pl-k\">for<\/span> <span class=\"pl-s1\">line<\/span> <span class=\"pl-c1\">in<\/span> <span class=\"pl-s1\">raw<\/span>.<span class=\"pl-c1\">strip<\/span>().<span class=\"pl-c1\">splitlines<\/span>() <span class=\"pl-k\">if<\/span> <span class=\"pl-s1\">line<\/span>.<span class=\"pl-c1\">strip<\/span>()]\n<span class=\"pl-en\">print<\/span>(<span class=\"pl-s1\">json<\/span>.<span class=\"pl-c1\">dumps<\/span>({\n    <span class=\"pl-s\">\"score\"<\/span>: <span class=\"pl-c1\">1.0<\/span> <span class=\"pl-k\">if<\/span> <span class=\"pl-en\">len<\/span>(<span class=\"pl-s1\">lines<\/span>) <span class=\"pl-c1\">==<\/span> <span class=\"pl-c1\">3<\/span> <span class=\"pl-k\">else<\/span> <span class=\"pl-c1\">0.0<\/span>,\n    <span class=\"pl-s\">\"metrics\"<\/span>: {<span class=\"pl-s\">\"line_count\"<\/span>: <span class=\"pl-en\">len<\/span>(<span class=\"pl-s1\">lines<\/span>)},\n    <span class=\"pl-s\">\"notes\"<\/span>: <span class=\"pl-s\">f\"<span class=\"pl-s1\"><span class=\"pl-kos\">{<\/span><span class=\"pl-en\">len<\/span>(<span class=\"pl-s1\">lines<\/span>)<span class=\"pl-kos\">}<\/span><\/span> non-empty line(s)\"<\/span>,\n}))\n<span class=\"pl-s1\">sys<\/span>.<span class=\"pl-c1\">exit<\/span>(<span class=\"pl-c1\">0<\/span> <span class=\"pl-k\">if<\/span> <span class=\"pl-en\">len<\/span>(<span class=\"pl-s1\">lines<\/span>) <span class=\"pl-c1\">==<\/span> <span class=\"pl-c1\">3<\/span> <span class=\"pl-k\">else<\/span> <span class=\"pl-c1\">1<\/span>)<\/pre>\n<\/div>\n<p dir=\"auto\">To run the eval, grade it and then view the results:<\/p>\n<div class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"smevals run my-eval -g                 # run every task, grade as each finishes&#10;smevals run my-eval -m gpt-4.1-nano -m gemini-2.5-flash -g   # more models&#10;smevals run my-eval -n 5 -g            # top every task up to five graded runs&#10;smevals report my-eval                 # markdown report in the terminal&#10;smevals serve my-eval                  # live web UI on http:\/\/127.0.0.1:7001\">\n<pre>smevals run my-eval -g                 <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> run every task, grade as each finishes<\/span>\nsmevals run my-eval -m gpt-4.1-nano -m gemini-2.5-flash -g   <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> more models<\/span>\nsmevals run my-eval -n 5 -g            <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> top every task up to five graded runs<\/span>\nsmevals report my-eval                 <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> markdown report in the terminal<\/span>\nsmevals serve my-eval                  <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> live web UI on http:\/\/127.0.0.1:7001<\/span><\/pre>\n<\/div>\n<p dir=\"auto\"><code>smevals run<\/code> executes the Runner once per Task\/model combination, with no arguments. Everything arrives through environment variables:<\/p>\n<ul dir=\"auto\">\n<li><code>SMEVALS_MODEL<\/code> &#8211; the model to use, from the Config or the <code>-m<\/code> option.<\/li>\n<li><code>SMEVALS_TASK<\/code> &#8211; the Task&#8217;s name.<\/li>\n<li><code>SMEVALS_PROMPT<\/code> &#8211; the Task&#8217;s <code>prompt<\/code>, only set if the Task has one.<\/li>\n<li><code>SMEVALS_TASK_&lt;KEY&gt;<\/code> &#8211; every scalar key of the Task, uppercased: a Task with <code>submission: mutant-003<\/code> provides <code>SMEVALS_TASK_SUBMISSION=mutant-003<\/code>.<\/li>\n<li><code>SMEVALS_RUN_DIR<\/code> &#8211; absolute path to the Run&#8217;s directory.<\/li>\n<\/ul>\n<p dir=\"auto\">The working directory is the Run&#8217;s directory. The contract:<\/p>\n<ul dir=\"auto\">\n<li>Standard output is captured as the Run&#8217;s <code>output.txt<\/code> &#8211; it should be the model&#8217;s response.<\/li>\n<li>Standard error is captured as <code>stderr.txt<\/code>.<\/li>\n<li>A non-zero exit code marks the Run as <strong>failed<\/strong>. A failed Run is a harness error &#8211; a network drop, a crashed tool &#8211; not evidence about the model, so it is never graded, is excluded from reports, and does not count towards an <code>-n<\/code> target (re-running the same command executes a replacement). Exit non-zero only for infrastructure problems; exit 0 whenever the output is a real model response you want judged, however bad.<\/li>\n<li>Any other files the Runner writes to its working directory are kept as Run artifacts (the <code>log.json<\/code> in the example above).<\/li>\n<\/ul>\n<p dir=\"auto\">A Runner that drives an agent harness instead of a plain model call follows the same contract: assemble whatever inputs the Task&#8217;s keys describe, run the harness, print the final result to standard output.<\/p>\n<p dir=\"auto\">A Grader is a YAML file in <code>graders\/<\/code>:<\/p>\n<div class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"name: default&#10;checks:&#10;  - checker: contains          # a built-in Checker, by name&#10;    value: &quot;&lt;svg&quot;&#10;    required: true&#10;  - checker: ..\/checkers\/render-svg   # a custom Checker, by path&#10;    input: extracted.svg&#10;    creates: render.png        # smevals verifies this file gets created&#10;    required: true&#10;  - checker: ..\/checkers\/llm-judge-image&#10;    image: render.png&#10;    model: gpt-4.1&#10;    rubric: Score this image from 0 to 10 ...&#10;scoring:&#10;  pass_threshold: 0.5\">\n<pre><span class=\"pl-ent\">name<\/span>: <span class=\"pl-s\">default<\/span>\n<span class=\"pl-ent\">checks<\/span>:\n  - <span class=\"pl-ent\">checker<\/span>: <span class=\"pl-s\">contains          <\/span><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> a built-in Checker, by name<\/span>\n    <span class=\"pl-ent\">value<\/span>: <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>&lt;svg<span class=\"pl-pds\">\"<\/span><\/span>\n    <span class=\"pl-ent\">required<\/span>: <span class=\"pl-c1\">true<\/span>\n  - <span class=\"pl-ent\">checker<\/span>: <span class=\"pl-s\">..\/checkers\/render-svg   <\/span><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> a custom Checker, by path<\/span>\n    <span class=\"pl-ent\">input<\/span>: <span class=\"pl-s\">extracted.svg<\/span>\n    <span class=\"pl-ent\">creates<\/span>: <span class=\"pl-s\">render.png        <\/span><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> smevals verifies this file gets created<\/span>\n    <span class=\"pl-ent\">required<\/span>: <span class=\"pl-c1\">true<\/span>\n  - <span class=\"pl-ent\">checker<\/span>: <span class=\"pl-s\">..\/checkers\/llm-judge-image<\/span>\n    <span class=\"pl-ent\">image<\/span>: <span class=\"pl-s\">render.png<\/span>\n    <span class=\"pl-ent\">model<\/span>: <span class=\"pl-s\">gpt-4.1<\/span>\n    <span class=\"pl-ent\">rubric<\/span>: <span class=\"pl-s\">Score this image from 0 to 10 ...<\/span>\n<span class=\"pl-ent\">scoring<\/span>:\n  <span class=\"pl-ent\">pass_threshold<\/span>: <span class=\"pl-c1\">0.5<\/span><\/pre>\n<\/div>\n<p dir=\"auto\">Each entry in <code>checks<\/code> names a Checker plus its configuration. Reserved keys:<\/p>\n<ul dir=\"auto\">\n<li><code>checker<\/code> &#8211; a built-in name, or a path to an executable relative to the Grader file.<\/li>\n<li><code>required<\/code> &#8211; if true and the Check fails, grading halts and the remaining Checks are recorded as skipped.<\/li>\n<li><code>creates<\/code> &#8211; a filename, or list of filenames, the Checker promises to create in the shared workspace; the Check fails if any of them do not appear. A Checker may write any number of additional files beyond those promised &#8211; everything in the workspace is kept as a Grade artifact.<\/li>\n<\/ul>\n<p dir=\"auto\">All other keys are configuration for the Checker, passed through via environment variables.<\/p>\n<ul dir=\"auto\">\n<li><code>contains<\/code> &#8211; passes if the Run&#8217;s <code>output.txt<\/code> contains <code>value<\/code>.<\/li>\n<li><code>xml-valid<\/code> &#8211; passes if <code>file<\/code> (looked up in the grade workspace, then the Run directory) parses as well-formed XML.<\/li>\n<\/ul>\n<p dir=\"auto\">The Grade&#8217;s outcome and score are computed as follows:<\/p>\n<ul dir=\"auto\">\n<li>The Grade&#8217;s score is the last <code>score<\/code> emitted by any Check &#8211; typically the final, most expensive Check. However, if any Check fails without emitting a score of its own, the Grade&#8217;s score is null: a stale score from an earlier Check never stands in for one that did not run.<\/li>\n<li>The outcome is <code>fail<\/code> if any Check failed, otherwise <code>pass<\/code> if the score meets <code>scoring.pass_threshold<\/code> (or if there is no threshold or no score).<\/li>\n<\/ul>\n<p dir=\"auto\"><code>smevals grade<\/code> executes each Check&#8217;s Checker with no arguments and these environment variables:<\/p>\n<ul dir=\"auto\">\n<li><code>SMEVALS_RUN_DIR<\/code> &#8211; absolute path to the Run directory being graded. Read the model&#8217;s output from <code>$SMEVALS_RUN_DIR\/output.txt<\/code>.<\/li>\n<li><code>SMEVALS_CHECK<\/code> &#8211; the full Check configuration as JSON, for structured values like lists.<\/li>\n<li><code>SMEVALS_CHECK_&lt;KEY&gt;<\/code> &#8211; every scalar key of the Check, uppercased: <code>rubric:<\/code> becomes <code>SMEVALS_CHECK_RUBRIC<\/code>.<\/li>\n<li><code>SMEVALS_TASK<\/code> and <code>SMEVALS_TASK_&lt;KEY&gt;<\/code> &#8211; the Task&#8217;s name and scalar keys, so a Checker can locate per-Task resources such as expected-answer files.<\/li>\n<\/ul>\n<p dir=\"auto\">The working directory is the grade workspace, shared by all Checks in the Grader in order: files written by one Check (a rendered image, an extracted document) are available to later Checks and are kept with the Grade as artifacts.<\/p>\n<p dir=\"auto\">A Checker signals pass or fail with its exit code (0 is a pass). It can also emit a JSON object on standard output with up to five keys, which are recorded in the Grade:<\/p>\n<ul dir=\"auto\">\n<li><code>score<\/code> &#8211; a float from 0.0 to 1.0.<\/li>\n<li><code>metrics<\/code> &#8211; an object mapping names to numbers or booleans, e.g. <code>{\"precision\": 0.9, \"status_correct\": true}<\/code>. Reports aggregate numbers as mean \u00b1 stderr and booleans as rates.<\/li>\n<li><code>tags<\/code> &#8211; a list of short labels, e.g. <code>[\"wearing_a_hat\", \"correct_bicycle_frame_shape\"]<\/code>. Tags are open vocabulary and presence-only: an absent tag means &#8220;not observed&#8221;, not &#8220;false&#8221;. They are normalized to lowercase snake_case, and the Grade records the union of all its Checks&#8217; tags. Reports aggregate them as counts and shares, and the web UI uses them for filtering.<\/li>\n<li><code>notes<\/code> &#8211; a human-readable string explaining the result. Never aggregated.<\/li>\n<li><code>details<\/code> &#8211; an object of structured diagnostics, such as predicted-versus-expected lists. Kept with the Grade but ignored by aggregation.<\/li>\n<\/ul>\n<p dir=\"auto\">Any other keys are folded into <code>details<\/code>. A Checker that fails may still emit a score (a partial-credit measurement); a Checker that crashes before scoring leaves the Grade unscored, as described above.<\/p>\n<p dir=\"auto\">Every Run is a directory:<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"runs\/&lt;task&gt;\/&lt;config&gt;\/&lt;model&gt;\/&lt;timestamp&gt;\/&#10;\u251c\u2500\u2500 run.yaml         # the record: full task, resolved config, timing, exit code&#10;\u251c\u2500\u2500 output.txt       # the model's response (runner stdout)&#10;\u251c\u2500\u2500 stderr.txt       # only present if the runner wrote to stderr&#10;\u251c\u2500\u2500 ...              # any other artifacts the runner wrote&#10;\u2514\u2500\u2500 grades\/&#10;    \u2514\u2500\u2500 &lt;grader&gt;\/&#10;        \u251c\u2500\u2500 grade.yaml     # outcome, score, tags, per-check results&#10;        \u251c\u2500\u2500 grader.yaml    # snapshot of the Grader that produced this Grade&#10;        \u2514\u2500\u2500 ...            # artifacts written by Checkers\">\n<pre class=\"notranslate\"><code>runs\/&lt;task&gt;\/&lt;config&gt;\/&lt;model&gt;\/&lt;timestamp&gt;\/\n\u251c\u2500\u2500 run.yaml         # the record: full task, resolved config, timing, exit code\n\u251c\u2500\u2500 output.txt       # the model's response (runner stdout)\n\u251c\u2500\u2500 stderr.txt       # only present if the runner wrote to stderr\n\u251c\u2500\u2500 ...              # any other artifacts the runner wrote\n\u2514\u2500\u2500 grades\/\n    \u2514\u2500\u2500 &lt;grader&gt;\/\n        \u251c\u2500\u2500 grade.yaml     # outcome, score, tags, per-check results\n        \u251c\u2500\u2500 grader.yaml    # snapshot of the Grader that produced this Grade\n        \u2514\u2500\u2500 ...            # artifacts written by Checkers\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">The model name is slugified for the path; the exact name is in <code>run.yaml<\/code>. <code>run.yaml<\/code> is written last, so its presence marks a complete Run. Runs are immutable &#8211; grading only ever adds files under <code>grades\/<\/code>.<\/p>\n<p dir=\"auto\">Each Grade includes a byte-for-byte snapshot of its Grader. <code>smevals grade<\/code> uses this for repeatability:<\/p>\n<ul dir=\"auto\">\n<li>By default it grades only Runs that have no Grade from the named Grader, and reports how many existing Grades were produced by an older version of the Grader spec.<\/li>\n<li><code>--regrade<\/code> deletes and re-creates every Grade for that Grader, so nothing stale survives. Use it after editing a Grader.<\/li>\n<li>Multiple Graders coexist: each grades into its own <code>grades\/&lt;name&gt;\/<\/code> directory, so an eval can have e.g. a cheap deterministic <code>default<\/code> grader and an LLM-judge <code>judge<\/code> grader side by side.<\/li>\n<\/ul>\n<p dir=\"auto\">By default <code>runs\/<\/code> lives inside the Eval directory. Pass <code>--runs-dir DIR<\/code> to <code>run<\/code>, <code>grade<\/code> and <code>report<\/code> to keep runs elsewhere; they are then namespaced by Eval name.<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"smevals run EVAL [-m MODEL]... [-c CONFIG] [-t TASK]... [-n N] [-g [GRADER]] [--runs-dir DIR]\">\n<pre class=\"notranslate\"><code>smevals run EVAL [-m MODEL]... [-c CONFIG] [-t TASK]... [-n N] [-g [GRADER]] [--runs-dir DIR]\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">Executes every Task (or just those named with <code>-t<\/code>) against every model given with <code>-m<\/code> (default: the Config&#8217;s model), using the Config named by <code>-c<\/code> (default: <code>default<\/code>). <code>-g<\/code> grades each Run the moment it finishes; <code>-g NAME<\/code> uses that Grader, bare <code>-g<\/code> uses <code>default<\/code>. Exits non-zero if any Run fails or grades as fail.<\/p>\n<p dir=\"auto\"><code>-n N<\/code> is a target sample size: each task\/model pair is topped up to at least N successful Runs, executing only the shortfall, so re-running the same command is a no-op once the target is met and an interrupted session can be resumed by repeating it. Runs execute in full passes over the pairs &#8211; interrupting partway leaves balanced samples rather than many Runs of the first Task and none of the last. Failed Runs (a non-zero Runner exit) do not count toward the target: re-running the command executes replacements for them, attempting each pair&#8217;s shortfall once per invocation, so a persistently failing Runner never retries in a loop. Without <code>-n<\/code>, exactly one new Run is executed per pair.<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"smevals grade EVAL [-g GRADER] [--regrade] [--runs-dir DIR]\">\n<pre class=\"notranslate\"><code>smevals grade EVAL [-g GRADER] [--regrade] [--runs-dir DIR]\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">Applies the Grader to every ungraded Run. Failed Runs are skipped &#8211; a harness error is not evidence worth grading. <code>--regrade<\/code> discards and redoes existing Grades from this Grader.<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"smevals report EVAL [-g GRADER] [--by-task] [--json] [--runs-dir DIR]\">\n<pre class=\"notranslate\"><code>smevals report EVAL [-g GRADER] [--by-task] [--json] [--runs-dir DIR]\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">Prints a markdown report: leaderboard of config \u00d7 model with mean \u00b1 stderr scores and failure counts, tag shares, and per-model blocks with metrics. Failed Runs are excluded from all statistics; the header reports how many were left out. <code>--by-task<\/code> adds per-task scores. <code>--json<\/code> emits the raw grade rows instead.<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"smevals serve EVAL_OR_SUITE... [-p PORT] [--host HOST] [-g GRADER]\">\n<pre class=\"notranslate\"><code>smevals serve EVAL_OR_SUITE... [-p PORT] [--host HOST] [-g GRADER]\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">Serves a live web UI (default port 7001) over one or more Evals. Data is re-read from disk on every poll, so the pages update as new Runs and Grades land. A directory that is not itself an Eval is treated as a Suite and searched recursively for Evals.<\/p>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"smevals build EVAL_OR_SUITE... [-o DIR] [-g GRADER]\">\n<pre class=\"notranslate\"><code>smevals build EVAL_OR_SUITE... [-o DIR] [-g GRADER]\n<\/code><\/pre>\n<\/div>\n<p dir=\"auto\">Builds the same web UI as a self-contained static site (default <code>build\/<\/code>), copying run artifacts into it. Each invocation adds or refreshes the given Evals in the output directory and leaves other Evals already built there untouched, so one site can aggregate Evals from many repositories.<\/p>\n<p dir=\"auto\">Outputs this document.<\/p>\n<\/div>\n<p><a href=\"https:\/\/github.com\/prime-radiant-inc\/smevals?utm_source=tldrai\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A framework for running evals against small (and large) models Or pip install smevals, or just uvx smevals &#8211;help. Vocabulary used by this project The top-level concept is an Eval: a collection of Tasks used to determine how good a particular model or model-and-harness configuration is at a specific high-level capability, such as text-to-SQL, drawing [&hellip;]<\/p>\n","protected":false},"author":16,"featured_media":22999,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[143],"tags":[],"class_list":["post-22998","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai"],"_links":{"self":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts\/22998","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/comments?post=22998"}],"version-history":[{"count":0,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/posts\/22998\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/media\/22999"}],"wp:attachment":[{"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/media?parent=22998"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/categories?post=22998"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scannn.com\/lv\/wp-json\/wp\/v2\/tags?post=22998"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}