Observed since 20 Oct, 15:37 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
3tears-evals
Evaluate LLM-backed products: campaigns over levers, judged and code-graded trials, cost and latency accounting, and analysis reports
Evals for LLM features, from a classifier to a tool-using agent: it runs each version over the same cases and says what moved, with the uncertainty that says whether the move is real. Python 3.14+.
pip install 3tears-evals
Rung 0 is one TOML file and no Python. examples/1-triage/triage.toml triages
30 support emails, each with the triage a person gave it, three times each:
# Rung 0, no Python: python -m threetears.evals triage.toml
cases = "emails.jsonl"
grader = ["category", "urgent", "needs_human"]
recording = "recordings/triage-toml.jsonl"
k = 3
[runner]
model = "deepseek/deepseek-v4-flash"
prompt = "Triage this support email:\n\n{email}"
[runner.output]
category = ["billing", "bug", "account", "sales", "spam"]
urgent = "bool"
needs_human = "bool"
python -m threetears.evals examples/1-triage/triage.toml replays the recorded model, offline and free, and its
first line says how it went:
triage: 16/30 cases passed; 14 misses: gdpr-delete, nonprofit, phish-suspend, phish-invoice, crash-at-checkout, and 9 more
Below it come each field's pass rate, each category label's precision and recall, the top misses with why each
missed, and where its report page is. Then climb the ladder, one idea per rung:
| Rung | Example | Climb it when |
|---|---|---|
| 1 | 1-triage/triage.py | Point it at your own function: 15 lines of Python. |
| 2 | 2-replies/ | When no code can grade the answer, let a model judge it, and check the judge against a person. |
| 3 | 3-invoices/ | Changing a prompt? Compare it against the current one, and get a report you can hand to a reviewer. |
| 4 | 4-booking/ | Is your agent doing the right thing to the world, not only saying it? Then serve your evals to the team. |
Where the science lives:
Wiring it into your app (your store, your launcher, your world): Adopting the engine.
pip install 3tears-evals is the base install. The FastMCP transport, for serving evals to an agent, is the [fastmcp] extra:
pip install "3tears-evals[fastmcp]"
To install a pre-release or an unreleased checkout, install from the repository at a tag instead:
pip install "3tears-evals @ git+https://github.com/pacepace/3tears.git@<tag>#subdirectory=packages/evals"
In a checkout of this repo, uv sync at the root does it, and the commands above run from packages/evals with
uv run in front. threetears.evals.__version__ says which version is installed. The completion client (Model) is
on the base install: it speaks each provider's HTTP API over httpx, with no SDK and no other 3tears package. The
examples run on cheap models through OpenRouter (OPENROUTER_API_KEY); Model(id, provider="anthropic")
(ANTHROPIC_API_KEY) and provider="openai" (OPENAI_API_KEY) are options, and base_url= takes any
OpenAI-compatible endpoint. After a pip install, fetch the examples from the repository first
(how).
The public API is still changing. From 0.70.0 only its host contract is promised: a name on it moves with a deprecation cycle, and any other declared name may change in any release, recorded in
MIGRATIONS.toml. About 520 names are declared, mapped by rung; the rest a root exports are provisional (stability tiers).
These links are relative to the package, so on GitHub each opens the page at the ref you are reading; on PyPI, where this README is the package's description, they do not resolve, so read the docs in the repository at your tag.
Climb
Go deeper
Background and reference
| Capability | API | Doc | Example |
|---|---|---|---|
| World contract | World(State, tools=[...]), each case's world key, expression graders; on a host, WorldRegistry | Evaluating a tool-using agent | 4-booking/ |
| World conformance kit | check_world_conformance (threetears.evals.kernel.host) | Step 5 | |
| Guardrails, decided apart from capability | grader(fn, guard=...), Judge(guard={dim: ...}), Result.guardrails(); on a host, MetricDescriptor(guardrail=True), RubricDim(axis="boundary") | Reading the guardrails | 3-invoices/ |
| Equivalence, the only verdict that says "good enough" | grader("label", margin=0.05), grader(fn, margin=...), grader(rating, range=(1, 5)); on a host, MetricDescriptor.materiality_threshold and value_range | Reading a comparison | 3-invoices/ |
| Typed verdicts and a CI gate | Result.verdicts(), Result.gate(), python -m threetears.evals gate --fail-on regressed,breached | gate | 3-invoices/ |
| Grids of factors | arms named as k=v pairs ("prompt=v2, effort=low"), Result.against | Start from the question | 3-invoices/ |
| Campaigns and declared designs | create_campaign, update_campaign, add_runs_to_campaign, declared_design, exploratory, control_from_run_id | Declare the design | |
| Cost against quality | Answer, max_cost_usd=, the frontier | Cost and budgets, the frontier | |
| Judge calibration and evidence tiers | Judge(labels=...), rate_result, judge_agreement, JudgeEvidenceTier | Judges and calibration | 2-replies/ |
| Analysis written by a model over frozen evidence | generate_analysis | Having a model write the analysis | llm_analysis.py |
| Cassettes | World(tools=[...], recording=...); on a host, cassette_mode="capture" / "replay" | Step 4, on a host | |
| MCP | threetears.evals.actions, mount_fastmcp (extra fastmcp) | Serving | 4-booking/serve.py |
| Keeping runs | Eval(...).run(store=path) (by default .evals/evals.sqlite); any DocumentStore, proven with STORE_CONFORMANCE_CASES | Keep your runs, the store | |
| A complete host | EvalHost, HostProfile, CandidateKind | Adopting the engine | courierhost, then toyhost |
Import only from the public roots the reference lists: a module below a root is internal and may move. A public name that leaves a root is recorded with its new home in each release's notes (Upgrading: names that move).
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
httpxEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.14",
"evaluation": "NOT_EVALUATED"
} | httpx>=0.27 | runtime |
pydanticEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.14",
"evaluation": "NOT_EVALUATED"
} | pydantic>=2.0 | runtime |
fastmcpEnvironment{
"extras": "",
"marker": "extra == \"fastmcp\"",
"requires_python": ">=3.14",
"evaluation": "NOT_EVALUATED"
} | fastmcp<4,>=3.4.2; extra == "fastmcp" | runtime |
bdist_wheel · 2,351.19 KB · Python py3
sdist · 4,190.31 KB · Python source
Shared repository data · GitHub
Public sponsor counts have not been collected. These signals belong to the repository owner.
github · 1 observations shown. Missing days are unknown.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 | 0 | 2026-10-20T17:18:19.8928356Z |
Observed since 20 Oct, 15:37 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:010df05c70c79baea6cfddcb341cebfcc99ad1a5ff4d2b25ce148983970205c1