Skip to main content

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 main baseline
  • 🔒 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: write permission 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

evalgate-0.3.0.tar.gz (32.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

evalgate-0.3.0-py3-none-any.whl (33.9 kB view details)

Uploaded Python 3

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

Hashes for evalgate-0.3.0.tar.gz
Algorithm Hash digest
SHA256 42165f7782fe4f570febf3916e3188fa8890c7eaa5ce7baeb432062a305cba3a
MD5 132e04899a65f70afde0ae57a59c9c24
BLAKE2b-256 05dafa2b6387261281ef70268f5638d8988739ae3d8393613c0c20c24429a3cf

See more details on using hashes here.

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

Hashes for evalgate-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a890104bfc6c1d2c0a2c7219ed0762e88763fac9e3a93fa09fd2601f3ee5734b
MD5 90e6f72a59466a5b6769d84a231bdeaa
BLAKE2b-256 5f940fa7d273e097415f0e7c32aa2213e198f765d9df5817ddca26d1f5adf07e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page