Deterministic LLM/RAG evals as a PR check
Project description
AOTP Ventures EvalGate
EvalGate runs deterministic LLM/RAG evals as a PR check. It compares your repo's generated outputs against fixtures, validates formatting, label accuracy, latency/cost budgets, and can use LLMs as judges for complex criteria. It posts a readable summary on the PR. Default is local-only (no telemetry).
- ✅ Deterministic checks (schema/labels/latency/cost)
- 🧠 LLM-based evaluation for complex criteria
- 🧪 Regression vs
mainbaseline - 🔒 Local-only by default; optional “metrics-only” later
- 🧰 Zero infra — a composite GitHub Action + tiny CLI
Quick Start
1. Install and Initialize
# Initialize EvalGate in your project
uvx --from evalgate evalgate init
# This creates:
# - .github/evalgate.yml (configuration)
# - eval/fixtures/ (test data with expected outputs)
# - eval/schemas/ (JSON schemas for validation)
2. Generate Your Model's Outputs
# Run your model/system to generate outputs for the fixtures
# (Replace with your actual prediction script)
python scripts/predict.py --in eval/fixtures --out .evalgate/outputs
3. Run Evaluation
# Run the evaluation suite
uvx --from evalgate evalgate run --config .github/evalgate.yml
# View results summary
uvx --from evalgate evalgate report --summary --artifact .evalgate/results.json
# Inside GitHub Actions, add --check-run to publish results as a check run
4. Update Baseline (optional)
When your fixtures or model outputs change, update the stored baseline results. This runs the evals and commits the results to the git ref specified by baseline.ref (default origin/main).
uvx --from evalgate evalgate baseline update --config .github/evalgate.yml
Pull requests will be compared against these baseline results.
Conversation Fixtures
When working with chat-based models, fixtures can describe full conversations.
Each conversation fixture contains a list of messages, where every message has
a role (such as system, user, assistant, or tool) and content.
Assistant messages may optionally include tool_calls describing functions the
assistant wants to invoke.
Example conversation fixture:
{
"messages": [
{ "role": "user", "content": "Hello!" },
{
"role": "assistant",
"content": "Hi there!",
"tool_calls": [
{ "name": "search", "arguments": { "query": "Hello!" } }
]
}
]
}
LLM as Judge
EvalGate can use LLMs to evaluate outputs for complex criteria beyond simple schema validation.
1. Install with LLM support
# Install with LLM dependencies
pip install evalgate[llm]
# Or with uv
uvx --from evalgate[llm] evalgate
2. Create a prompt template
Create an evaluation prompt in eval/prompts/quality_judge.txt:
You are evaluating the quality of customer support responses.
INPUT:
{input}
EXPECTED:
{expected}
OUTPUT:
{output}
Rate from 0.0 to 1.0 based on accuracy, helpfulness, and tone.
Score: [your score]
3. Configure your evaluator
In .github/evalgate.yml:
evaluators:
# Your existing evaluators...
- name: content_quality
type: llm
provider: openai # or anthropic, azure, local
model: gpt-4 # or other models
prompt_path: eval/prompts/quality_judge.txt
api_key_env_var: OPENAI_API_KEY
weight: 0.3
min_score: 0.75 # fail if score < 0.75
min_score enforces a minimum evaluator score; the run fails if the score drops below this threshold.
4. Set your API key
export OPENAI_API_KEY=your_api_key_here
5. Run evaluation
evalgate run --config .github/evalgate.yml
LLM judge responses are cached in .evalgate/cache.json. Reuse cached results to avoid repeat API calls or clear them with:
evalgate run --config .github/evalgate.yml --clear-cache
GitHub Actions Integration with API Keys
Add your API keys as repository secrets in GitHub, then use them in your workflow:
# Optional: Validate API key is set (fail fast with clear message)
- name: Validate OpenAI API Key
run: |
if [ -z "${{ secrets.OPENAI_API_KEY }}" ]; then
echo "❌ OPENAI_API_KEY secret is not set"
echo "Please add your OpenAI API key as a repository secret named 'OPENAI_API_KEY'"
echo "Go to: Settings > Secrets and variables > Actions > New repository secret"
exit 1
fi
- name: Run EvalGate with LLM judge
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: uvx --from evalgate[llm] evalgate run --config .github/evalgate.yml
Or with the composite action:
- uses: aotp-ventures/evalgate@main
with:
config: .github/evalgate.yml
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
check_run: true
Tool Usage Logs
Model outputs can record tool invocations to enable deterministic evaluation of agent behavior. Each output may include a tool_calls array with call name and args in the order executed:
{
"output": "...",
"tool_calls": [
{"name": "search", "args": {"query": "foo"}},
{"name": "lookup", "args": {"id": 1}}
]
}
The tool_usage evaluator compares these logs against expected sequences via the expected_tool_calls config field.
GitHub Actions Integration
Option 1: Use the Composite Action
Add this to your .github/workflows/ directory:
name: EvalGate
on: [pull_request]
jobs:
evalgate:
runs-on: ubuntu-latest
outputs:
total_score: ${{ steps.evalgate.outputs.total_score }}
passed: ${{ steps.evalgate.outputs.passed }}
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
# Generate your model outputs
- name: Generate outputs
run: python scripts/predict.py --in eval/fixtures --out .evalgate/outputs
# Run EvalGate
- uses: aotp-ventures/evalgate@main
id: evalgate
with:
config: .github/evalgate.yml
check_run: true
build:
needs: evalgate
if: ${{ needs.evalgate.outputs.passed == 'true' }}
runs-on: ubuntu-latest
steps:
- run: echo "EvalGate score ${{ needs.evalgate.outputs.total_score }}"
Note: The workflow requires
checks: writepermission to publish the check run.
Option 2: Direct Integration
Or integrate directly in your existing workflow:
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run EvalGate
id: evalgate
run: |
uvx --from evalgate evalgate run --config .github/evalgate.yml
total_score=$(jq -r '.overall' .evalgate/results.json)
passed=$(jq -r '.gate.passed' .evalgate/results.json)
echo "total_score=$total_score" >> "$GITHUB_OUTPUT"
echo "passed=$passed" >> "$GITHUB_OUTPUT"
- name: EvalGate Summary
if: always()
run: uvx --from evalgate evalgate report --summary --artifact ./.evalgate/results.json
- name: Continue if passed
if: ${{ steps.evalgate.outputs.passed == 'true' }}
run: echo "EvalGate score ${{ steps.evalgate.outputs.total_score }}"
# Optional: Upload detailed results for debugging
- name: Upload EvalGate Results
if: always()
uses: actions/upload-artifact@v4
with:
name: evalgate-results
path: .evalgate/results.json
retention-days: 30
## Conversation Flow Evaluator
Validate multi-turn conversations by checking the final message and turn count.
```yaml
evaluators:
- name: convo_flow
type: conversation
expected_final_field: content
max_turns: 5
weight: 0.2
Each output must provide a messages array. The evaluator compares the last
message's content against the fixture's expected.content and fails if the
conversation exceeds max_turns.
Writing a custom evaluator
EvalGate supports custom evaluators through the plugin registry introduced in Issue 1. This lets you package and share evaluation logic as reusable plugins.
from evalgate.plugins import registry
from evalgate.evaluators import BaseEvaluator
@registry.evaluator("my_custom")
class MyCustomEvaluator(BaseEvaluator):
def evaluate(self, outputs, fixtures, **kwargs):
score = 1.0 # your scoring logic here
violations: list[str] = []
return score, violations
After registering, reference the evaluator in your configuration:
evaluators:
- name: my_custom
type: my_custom
weight: 0.5
Refreshing your baseline
EvalGate compares pull requests against a baseline stored on your main branch. When your model's expected outputs change, refresh the baseline so future PRs compare against the new results:
# Generate fresh outputs and update baseline
python scripts/predict.py --in eval/fixtures --out .evalgate/outputs
uvx --from evalgate evalgate run --config .github/evalgate.yml
git add .evalgate/results.json
git commit -m "Refresh eval baseline"
Merge the commit into main, and subsequent runs will use the updated baseline for regression checks.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file evalgate-0.3.0.tar.gz.
File metadata
- Download URL: evalgate-0.3.0.tar.gz
- Upload date:
- Size: 32.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42165f7782fe4f570febf3916e3188fa8890c7eaa5ce7baeb432062a305cba3a
|
|
| MD5 |
132e04899a65f70afde0ae57a59c9c24
|
|
| BLAKE2b-256 |
05dafa2b6387261281ef70268f5638d8988739ae3d8393613c0c20c24429a3cf
|
File details
Details for the file evalgate-0.3.0-py3-none-any.whl.
File metadata
- Download URL: evalgate-0.3.0-py3-none-any.whl
- Upload date:
- Size: 33.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a890104bfc6c1d2c0a2c7219ed0762e88763fac9e3a93fa09fd2601f3ee5734b
|
|
| MD5 |
90e6f72a59466a5b6769d84a231bdeaa
|
|
| BLAKE2b-256 |
5f940fa7d273e097415f0e7c32aa2213e198f765d9df5817ddca26d1f5adf07e
|