Skip to content

Commit a0197ec

Browse files
committed
Improve SEO positioning and add LLM eval landing pages
1 parent efe94c3 commit a0197ec

13 files changed

Lines changed: 425 additions & 77 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ Thumbs.db
2525

2626
doc/prd.md
2727
doc/task.md
28+
SEO_GROWTH_PLAN.md
29+
parthmax-journey.md

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
</picture>
66
</p>
77

8-
<h3 align="center">pytest for LLMs</h3>
9-
<p align="center">Deterministic record/replay and semantic assertions for GenAI apps — local-first, privacy-first, zero SaaS lock-in.</p>
8+
<h3 align="center">CI-native LLM evals for real applications.</h3>
9+
<p align="center">ghostrun turns real LLM app behavior into deterministic pytest evals: record API calls once, replay them in CI, and catch semantic regressions before they ship.</p>
1010

1111
<p align="center">
1212
<a href="https://pypi.org/project/ghostrun/"><img alt="PyPI" src="https://img.shields.io/pypi/v/ghostrun.svg"></a>
13+
<a href="https://pypi.org/project/ghostrun/"><img alt="PyPI downloads" src="https://static.pepy.tech/badge/ghostrun"></a>
1314
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
1415
<a href="pyproject.toml"><img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-blue.svg"></a>
1516
<a href="https://github.com/parthmax2/ghostrun/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/parthmax2/ghostrun/actions/workflows/ci.yml/badge.svg"></a>
@@ -31,7 +32,7 @@
3132
---
3233

3334
> [!TIP]
34-
> Every real test run of an LLM feature means a live API call — slow, costs money, and flaky, since the model never says the same thing twice. ghostrun records the call once, replays it instantly forever after, and grades the *meaning* of the output instead of matching exact text.
35+
> LLM evals often live outside the product: separate datasets, dashboards, YAML files, and one-off scripts. ghostrun keeps evals inside normal `pytest`, runs them against the real code path your users hit, records the live LLM call once, and replays it deterministically in CI.
3536
3637
### The problem
3738

@@ -40,7 +41,9 @@ reply = generate_reply("Where is my refund?")
4041
assert reply == "I'm sorry for the delay..." # fails tomorrow: LLM never says the same thing twice
4142
```
4243

43-
### What ghostrun does about it
44+
LLM apps need evals, but live model calls make test suites painful: they cost money, need API keys in CI, run slowly, and fail for reasons unrelated to your code. Generic eval platforms are powerful, but they often test a prompt, dataset, or trace outside your application. ghostrun focuses on app-native LLM regression testing: the exact Python code path your users exercise.
45+
46+
### How ghostrun fixes it
4447

4548
1. **Deterministic replay** — the first run records real LLM HTTP calls to a local `.ghostrun_cache/`; every run after replays them instantly from disk. Zero API cost, zero latency, zero flakiness, no key needed in CI.
4649
2. **Semantic assertions** — assert on *meaning*, not exact text:
@@ -52,6 +55,8 @@ assert reply == "I'm sorry for the delay..." # fails tomorrow: LLM never says
5255

5356
No cloud dashboard. No custom CLI to learn. No dataset to author by hand. Just `pytest`.
5457

58+
**Search terms this project is built for:** LLM evals in CI, LLM regression testing, pytest LLM evals, deterministic LLM tests, prompt regression testing, semantic assertions for LLM apps, and testing OpenAI or Anthropic applications with pytest.
59+
5560
---
5661

5762
## Install
@@ -135,6 +140,9 @@ Start here, in order:
135140

136141
| Guide | What's in it |
137142
| :--- | :--- |
143+
| [LLM regression testing](https://ghostrun.parthmax.in/guide/llm-regression-testing.html) | CI-native LLM evals for catching semantic and prompt regressions in real app code |
144+
| [Pytest LLM evals](https://ghostrun.parthmax.in/guide/pytest-llm-evals.html) | How to write LLM evals as normal pytest tests instead of dashboard-only workflows |
145+
| [Test OpenAI apps offline](https://ghostrun.parthmax.in/guide/test-openai-apps-offline.html) | Record/replay OpenAI and Anthropic API calls so CI does not repeat live model calls |
138146
| [doc/guide/recording.md](doc/guide/recording.md) | How record/replay works, judge-verdict caching, supported providers, secret redaction, parallel test runs |
139147
| [doc/guide/assertions.md](doc/guide/assertions.md) | Semantic assertions, judge reliability (benchmarked, not asserted), majority-vote verdicts, tool/function-call assertions |
140148
| [doc/guide/configuration.md](doc/guide/configuration.md) | `.ghostrun.yaml`, environment variables, pytest flags, `ghostrun doctor`, `ghostrun init` |
@@ -156,7 +164,7 @@ Start here, in order:
156164
</details>
157165

158166
A hosted, searchable version of this documentation is planned at
159-
[parthmax2.github.io/ghostrun](https://parthmax2.github.io/ghostrun/) (config
167+
[ghostrun.parthmax.in](https://ghostrun.parthmax.in/) (config
160168
in `mkdocs.yml`, builds via `.github/workflows/docs.yml`).
161169

162170
## Roadmap

llms.txt

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,39 @@
11
# ghostrun
22

3-
> pytest for LLMs: deterministic HTTP record/replay, local LLM-as-judge semantic assertions, and prompt-version regression diffing for testing GenAI applications in Python.
4-
5-
ghostrun solves two problems that make LLM applications hard to test with
6-
ordinary `assert`: non-deterministic output (the same prompt returns different
7-
text every run) and cost/latency (every test run would otherwise hit a paid,
8-
slow API). It intercepts LLM provider HTTP calls at the transport layer
9-
(httpx) — not by monkey-patching the OpenAI/Anthropic SDKs — so the first test
10-
run records a real response and every run after replays it in milliseconds
11-
with no API key, no network call, and no cost. Semantic assertions
12-
(`ghostrun.expect(reply).contains_intent("apology")`) are graded by a judge —
13-
by default a local Ollama model, so prompts and responses never leave the
14-
machine — and judge verdicts are themselves cached with an optional
15-
majority-of-k voting mode, benchmarked against a real judge rather than
16-
assumed to work (see the benchmark write-up below). `ghostrun diff` compares
17-
two recorded runs to flag which assertions regressed, which fixed, and which
18-
LLM outputs drifted, even when they still pass.
19-
20-
It is a Python package (`pip install ghostrun`), a pytest plugin (auto-registers
21-
via the `pytest11` entry point, no conftest wiring needed), and a CLI
22-
(`ghostrun list|show|diff|doctor|init`). It is not a hosted SaaS, not an
23-
observability platform, and not a cross-model benchmarking tool — see the
24-
comparison document below for how it relates to DeepEval, Promptfoo, Ragas,
25-
vcr-langchain, and other tools in this space.
3+
> CI-native LLM evals for real applications: record LLM API calls once, replay them deterministically in pytest, and catch semantic regressions before they ship.
4+
5+
ghostrun is a Python package, pytest plugin, and CLI for app-native LLM regression testing. It is built for developers who want LLM evals inside the software test suite instead of a separate dataset, dashboard, YAML workflow, or one-off script.
6+
7+
ghostrun tests the real Python code path your users hit. The first run records live OpenAI, Anthropic, Gemini, Bedrock, Ollama, or other LLM provider HTTP calls at the transport layer. Later runs replay those calls deterministically in pytest and CI, so evals are fast, repeatable, and cheap enough to run on every pull request.
8+
9+
Important search phrases: LLM evals in CI, LLM regression testing, pytest LLM evals, deterministic LLM tests, prompt regression testing, semantic assertions for LLM apps, testing OpenAI apps with pytest, testing Anthropic apps with pytest, app-native LLM evals, GenAI testing, AI testing, prompt testing, and record/replay LLM API calls.
10+
11+
Semantic assertions such as `ghostrun.expect(reply).contains_intent("apology")` grade meaning rather than exact text. Judge verdicts are cached, and optional majority-of-k voting is available for higher-signal semantic checks. `ghostrun diff` compares recorded runs to flag assertion regressions, fixes, stable checks, removed checks, and LLM output drift.
12+
13+
ghostrun is not a hosted SaaS, not an observability platform, and not a cross-model leaderboard. Compared with DeepEval, Promptfoo, Ragas, Langfuse, Braintrust, and LangSmith, ghostrun's focused wedge is app-native regression evals in normal pytest: real code, real LLM calls, deterministic replay, semantic assertions, and CI-friendly failure output.
2614

2715
## Core documentation
2816

29-
- [README](README.md): what it is, install, quickstart, documentation index
30-
- [Recording and replay](doc/guide/recording.md): HTTP-transport interception, judge-verdict caching, supported providers, secret redaction, parallel test runs
31-
- [Semantic assertions](doc/guide/assertions.md): `expect()` API, measured judge reliability (~90% on hand-labeled cases), majority-vote verdicts, tool-call assertions
32-
- [Prompt regression tracking](doc/guide/regression-tracking.md): run snapshots, `ghostrun diff`, CI/PR integration (GitHub Actions, JUnit XML)
17+
- [README](README.md): install, quickstart, positioning, and documentation index
18+
- [LLM regression testing](site/guide/llm-regression-testing.html): CI-native LLM evals for catching semantic and prompt regressions in real app code
19+
- [Pytest LLM evals](site/guide/pytest-llm-evals.html): write LLM evals as normal pytest tests
20+
- [Test OpenAI apps offline](site/guide/test-openai-apps-offline.html): record/replay OpenAI and Anthropic API calls so CI does not repeat live model calls
21+
- [Recording and replay](doc/guide/recording.md): HTTP transport interception, judge-verdict caching, supported providers, secret redaction, parallel test runs
22+
- [Semantic assertions](doc/guide/assertions.md): `expect()` API, judge reliability, majority-vote verdicts, tool-call assertions
23+
- [Prompt regression tracking](doc/guide/regression-tracking.md): run snapshots, `ghostrun diff`, CI/PR integration, GitHub Actions, JUnit XML
3324
- [Configuration](doc/guide/configuration.md): `.ghostrun.yaml`, environment variables, `ghostrun doctor`, `ghostrun init`
3425
- [API reference](doc/guide/api-reference.md): every public function, class, and config field
3526

3627
## Research and rationale
3728

38-
- [Why not just ask an LLM to write this?](doc/guide/why-not-diy.md): concrete bugs found in this project's own development (thread-safety races, secret-redaction false positives, non-monotonic majority voting) as the case for a maintained package over a generated script
39-
- [Judge-voting benchmark](doc/judge-voting-benchmark.md): full methodology and results for whether majority-vote judge caching actually improves reliability (answer: it's a better detector of unreliable assertions than a fix for them)
40-
- [Comparison with other tools](doc/comparison.md): researched, sourced comparison against DeepEval, Promptfoo, Ragas, vcr-langchain, Langfuse, Braintrust, LangSmith, Giskard, and others
41-
42-
## Project status
43-
44-
- [Changelog](CHANGELOG.md): release notes
29+
- [Why not just ask an LLM to write this?](doc/guide/why-not-diy.md): concrete bugs found in this project's own development
30+
- [Judge-voting benchmark](doc/judge-voting-benchmark.md): methodology and results for majority-vote judge caching
31+
- [Comparison with other tools](doc/comparison.md): sourced comparison against DeepEval, Promptfoo, Ragas, vcr-langchain, Langfuse, Braintrust, LangSmith, Giskard, and others
4532

46-
## Optional
33+
## Project links
4734

48-
- [Bundled example](examples/README.md): a runnable customer-support test with a pre-recorded cache, plus a live-API smoke test
35+
- Website: https://ghostrun.parthmax.in
36+
- Source: https://github.com/parthmax2/ghostrun
37+
- PyPI: https://pypi.org/project/ghostrun/
38+
- Changelog: CHANGELOG.md
39+
- License: MIT

mkdocs.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
site_name: ghostrun
22
site_description: >-
3-
pytest for LLMs: deterministic HTTP record/replay, local LLM-as-judge
4-
semantic assertions, and prompt regression diffing for testing GenAI
5-
applications in Python.
6-
site_url: https://parthmax2.github.io/ghostrun/
3+
CI-native LLM evals for real applications: record LLM API calls once,
4+
replay them deterministically in pytest, and catch semantic regressions
5+
before they ship.
6+
site_url: https://ghostrun.parthmax.in/
77
repo_url: https://github.com/parthmax2/ghostrun
88
repo_name: parthmax2/ghostrun
99
edit_uri: edit/main/doc/

pyproject.toml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ build-backend = "hatchling.build"
55
[project]
66
name = "ghostrun"
77
version = "0.1.1"
8-
description = "pytest for LLMs: deterministic HTTP record/replay, local LLM-as-judge semantic assertions, and prompt regression diffing for testing GenAI applications."
8+
description = "CI-native LLM evals for real apps: record LLM API calls once, replay them deterministically in pytest, and catch semantic regressions before they ship."
99
readme = "README.md"
1010
requires-python = ">=3.9"
1111
license = { text = "MIT" }
1212
authors = [{ name = "parthmax2" }]
1313
keywords = [
14-
"llm", "llm-testing", "llm-eval", "llm-evaluation", "llm-as-judge",
15-
"pytest", "pytest-plugin", "testing", "unit-testing",
16-
"genai", "generative-ai", "ai-testing", "agent-testing",
17-
"openai", "anthropic", "ollama",
14+
"llm", "llm-evals", "llm-eval", "llm-evaluation", "llm-as-judge",
15+
"llm-testing", "llm-regression-testing", "pytest", "pytest-plugin",
16+
"testing", "unit-testing", "ci", "ci-cd",
17+
"genai", "generative-ai", "ai-testing", "agent-testing", "app-testing",
18+
"openai", "anthropic", "ollama", "chatbot-testing",
1819
"record-replay", "vcr", "http-mocking", "http-cache",
19-
"prompt-testing", "prompt-regression", "regression-testing",
20-
"semantic-assertions", "deterministic-testing", "ci-cd",
20+
"prompt-testing", "prompt-regression", "prompt-regression-testing",
21+
"regression-testing", "semantic-assertions", "deterministic-testing",
22+
"deterministic-llm-tests", "offline-testing",
2123
]
2224
classifiers = [
2325
"Development Status :: 3 - Alpha",
@@ -48,10 +50,13 @@ dev = ["pytest>=7.0"]
4850
ghostrun = "ghostrun.cli:main"
4951

5052
[project.urls]
51-
Homepage = "https://github.com/parthmax2/ghostrun"
53+
Homepage = "https://ghostrun.parthmax.in"
54+
Source = "https://github.com/parthmax2/ghostrun"
5255
Issues = "https://github.com/parthmax2/ghostrun/issues"
53-
Documentation = "https://github.com/parthmax2/ghostrun#documentation"
56+
Documentation = "https://ghostrun.parthmax.in"
5457
Changelog = "https://github.com/parthmax2/ghostrun/blob/main/CHANGELOG.md"
58+
Repository = "https://github.com/parthmax2/ghostrun"
59+
Author = "https://github.com/parthmax2"
5560

5661
# Register the pytest plugin so `import ghostrun` isn't required to activate it.
5762
[project.entry-points.pytest11]

site/assets/search-index.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ window.GHOSTRUN_SEARCH_INDEX = [
33
title: "ghostrun",
44
section: "Home",
55
url: "index.html",
6-
excerpt: "pytest for LLMs — deterministic record/replay and semantic assertions, local-first and privacy-first.",
7-
keywords: "home overview pitch"
6+
excerpt: "CI-native LLM evals for real applications: record API calls once, replay in CI, and catch semantic regressions.",
7+
keywords: "home overview pitch llm evals ci regression testing pytest"
88
},
99
{
1010
title: "Getting started",
@@ -13,6 +13,27 @@ window.GHOSTRUN_SEARCH_INDEX = [
1313
excerpt: "Install ghostrun, pull an Ollama model, and scaffold a working first test with ghostrun init.",
1414
keywords: "install pip ollama init quickstart pytest"
1515
},
16+
{
17+
title: "LLM regression testing",
18+
section: "Guide",
19+
url: "guide/llm-regression-testing.html",
20+
excerpt: "Catch LLM behavior regressions in CI by recording real app calls, replaying them deterministically, and asserting on meaning.",
21+
keywords: "llm regression testing prompt regression testing deterministic llm tests ci semantic regression"
22+
},
23+
{
24+
title: "Pytest LLM evals",
25+
section: "Guide",
26+
url: "guide/pytest-llm-evals.html",
27+
excerpt: "Write LLM evals as normal pytest tests around the real application code path your users hit.",
28+
keywords: "pytest llm evals llm testing pytest llm evals python semantic assertions"
29+
},
30+
{
31+
title: "Test OpenAI apps offline",
32+
section: "Guide",
33+
url: "guide/test-openai-apps-offline.html",
34+
excerpt: "Record real OpenAI and Anthropic API calls once, then replay them offline and deterministically in pytest.",
35+
keywords: "test openai app pytest mock openai calls pytest record replay openai api calls offline llm tests"
36+
},
1637
{
1738
title: "Recording and replay",
1839
section: "Guide",

0 commit comments

Comments
 (0)