|
| 1 | +# Concepts |
| 2 | + |
| 3 | +This page explains the mental model behind evalwire before you dive into the quick start or API reference. |
| 4 | + |
| 5 | +## The four building blocks |
| 6 | + |
| 7 | +### Datasets |
| 8 | + |
| 9 | +A **dataset** is a named collection of test examples stored in Arize Phoenix. Each example has input fields (e.g. `user_query`) and expected-output fields (e.g. `expected_output`). evalwire creates datasets by uploading a CSV testset and splitting it by a tag column. |
| 10 | + |
| 11 | +One CSV row can belong to multiple datasets. Just pipe-delimit the tag value: |
| 12 | + |
| 13 | +``` |
| 14 | +user_query,expected_output,tags |
| 15 | +"find cycling paths","url-a | url-b","es_search | source_router" |
| 16 | +``` |
| 17 | + |
| 18 | +This row appears in both the `es_search` and `source_router` datasets. |
| 19 | + |
| 20 | +### Experiments |
| 21 | + |
| 22 | +An **experiment** is the result of running a task against a dataset and scoring each output with one or more evaluators. Phoenix records every experiment run with a timestamp so you can compare results across code changes. |
| 23 | + |
| 24 | +evalwire discovers experiments by scanning a directory. Each subdirectory that contains a `task.py` is treated as one experiment. The directory name must match a Phoenix dataset name: |
| 25 | + |
| 26 | +``` |
| 27 | +experiments/ |
| 28 | + es_search/ ← matched to the "es_search" Phoenix dataset |
| 29 | + task.py |
| 30 | + top_k.py ← evaluator |
| 31 | + source_router/ ← matched to the "source_router" Phoenix dataset |
| 32 | + task.py |
| 33 | + is_in.py ← evaluator |
| 34 | +``` |
| 35 | + |
| 36 | +### Tasks |
| 37 | + |
| 38 | +A **task** is an `async` function that receives a Phoenix example object and returns the output to be scored. Its job is to call your system under test and return a result in whatever form your evaluators expect. |
| 39 | + |
| 40 | +```python |
| 41 | +# experiments/es_search/task.py |
| 42 | +async def task(example): |
| 43 | + user_query = example.input["user_query"] |
| 44 | + return await my_retrieval_function(user_query) |
| 45 | +``` |
| 46 | + |
| 47 | +The function must be named `task`. |
| 48 | + |
| 49 | +### Evaluators |
| 50 | + |
| 51 | +An **evaluator** is a plain callable with the signature: |
| 52 | + |
| 53 | +```python |
| 54 | +def evaluator(output, expected: dict) -> float | bool: ... |
| 55 | +``` |
| 56 | + |
| 57 | +Each evaluator file in an experiment directory is auto-loaded. The callable must share its name with the file: |
| 58 | + |
| 59 | +```python |
| 60 | +# experiments/es_search/top_k.py |
| 61 | +from evalwire.evaluators import make_top_k_evaluator |
| 62 | + |
| 63 | +top_k = make_top_k_evaluator(K=5) |
| 64 | +``` |
| 65 | + |
| 66 | +Return `float` (0.0–1.0) for graded scoring or `bool` for pass/fail. Phoenix displays both. |
| 67 | + |
| 68 | +## The experiment lifecycle |
| 69 | + |
| 70 | +``` |
| 71 | +CSV testset |
| 72 | + │ |
| 73 | + │ evalwire upload |
| 74 | + ▼ |
| 75 | +Phoenix Datasets (one per unique tag) |
| 76 | + │ |
| 77 | + │ evalwire run |
| 78 | + ▼ |
| 79 | +Task function (called once per example) |
| 80 | + │ |
| 81 | + ▼ |
| 82 | +Output |
| 83 | + │ |
| 84 | + │ evaluator(output, expected) |
| 85 | + ▼ |
| 86 | +Experiment Results (stored in Phoenix, visible in UI) |
| 87 | +``` |
| 88 | + |
| 89 | +1. **Upload**: `evalwire upload` reads your CSV, groups rows by the tag column, and creates or updates one Phoenix dataset per unique tag value. |
| 90 | +2. **Run**: `evalwire run` scans the experiments directory, matches each subdirectory to a Phoenix dataset by name, calls `task` on every example, and scores the output with each evaluator file found in that directory. |
| 91 | +3. **Compare**: open the Phoenix UI, navigate to the dataset, and switch to the **Experiments** tab to compare runs side-by-side. |
| 92 | + |
| 93 | +## How `expected` is structured |
| 94 | + |
| 95 | +Inside every evaluator, the `expected` parameter is a dict containing all output columns from the original CSV row. The most important key is `"expected_output"`, which evalwire parses with `_parse_expected`: |
| 96 | + |
| 97 | +- Plain string `"answer"` → `["answer"]` |
| 98 | +- Pipe-delimited string `"a | b"` → `["a", "b"]` |
| 99 | +- Python-literal string `"['a', 'b']"` → `["a", "b"]` |
| 100 | +- Already a list → returned as-is |
| 101 | + |
| 102 | +Most built-in evaluators read `expected["expected_output"]` for you. When writing a custom evaluator you can also access any other column directly: `expected["my_column"]`. |
0 commit comments