-
Notifications
You must be signed in to change notification settings - Fork 7
feat: fixture-based evaluation harness #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Benkapner
wants to merge
3
commits into
main
Choose a base branch
from
feat/evals
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| name: Eval | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| case: | ||
| description: "Run a specific case (leave empty for all)" | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| eval: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 | ||
| - run: uv python install 3.12 | ||
| - run: uv sync --extra dev | ||
|
|
||
| - name: Run evals | ||
| env: | ||
| MODEL_API_BASE: ${{ secrets.MODEL_API_BASE }} | ||
| MODEL_API_KEY: ${{ secrets.MODEL_API_KEY }} | ||
| MODEL_NAME: ${{ secrets.MODEL_NAME }} | ||
| run: | | ||
| args="--verbose" | ||
| if [ -n "${{ inputs.case }}" ]; then | ||
| args="$args --case ${{ inputs.case }}" | ||
| fi | ||
| uv run python evals/run.py $args | ||
|
|
||
| - name: Upload results | ||
| if: always() | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||
| with: | ||
| name: eval-results | ||
| path: evals/results/ | ||
| if-no-files-found: ignore | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Evaluation Harness | ||
|
|
||
| Fixture-based evaluation for code-to-docs prompt quality. Tests run against | ||
| a real LLM endpoint and check assertions rather than comparing golden text. | ||
|
|
||
| ## Running | ||
|
|
||
| ```bash | ||
| # Set endpoint credentials | ||
| export MODEL_API_BASE="https://your-endpoint/v1" | ||
| export MODEL_API_KEY="your-key" | ||
| export MODEL_NAME="your-model" | ||
|
|
||
| # Run all cases | ||
| uv run python evals/run.py | ||
|
|
||
| # Run a single case | ||
| uv run python evals/run.py --case issue-52-deletion --verbose | ||
| ``` | ||
|
|
||
| ## Adding a Case | ||
|
|
||
| Create a directory under `evals/fixtures/` with: | ||
|
|
||
| ``` | ||
| evals/fixtures/my-case/ | ||
| diff.patch # The code diff to analyze | ||
| before/ # Doc files in their original state | ||
| docs/guide.md | ||
| docs/api.md | ||
| expectations.yaml # Assertions to check | ||
| instructions.txt # (optional) User instructions | ||
| ``` | ||
|
|
||
| ### expectations.yaml format | ||
|
|
||
| ```yaml | ||
| # Files that should be updated (not return NO_UPDATE_NEEDED) | ||
| selected: | ||
| - docs/guide.md | ||
|
|
||
| # Files that should NOT be updated | ||
| not_selected: | ||
| - docs/unrelated.md | ||
|
|
||
| # Content checks on the generated output | ||
| content_checks: | ||
| docs/guide.md: | ||
| contains: | ||
| - "new-flag" | ||
| not_contains: | ||
| - "deleted heading" | ||
| heading_present: | ||
| - "## Configuration" | ||
|
|
||
| # If true, all files should return NO_UPDATE_NEEDED | ||
| expect_no_update: false | ||
| ``` | ||
|
|
||
| Assertions are preferred over golden files because model output varies | ||
| across runs and model versions. Check for structural properties (headings | ||
| present, keywords included, sections preserved) rather than exact text. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # CLI Reference | ||
|
|
||
| ## Usage | ||
|
|
||
| ``` | ||
| mytool [options] <input> | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| | Flag | Description | | ||
| |------|-------------| | ||
| | `--verbose`, `-v` | Enable verbose output | | ||
| | `--help` | Show help message | | ||
|
|
||
| ## Examples | ||
|
|
||
| ```bash | ||
| # Basic usage | ||
| mytool data.csv | ||
|
|
||
| # Verbose mode | ||
| mytool -v data.csv | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| diff --git a/src/cli.py b/src/cli.py | ||
| index 1112222..3334444 100644 | ||
| --- a/src/cli.py | ||
| +++ b/src/cli.py | ||
| @@ -35,6 +35,12 @@ def build_parser(): | ||
| "--verbose", "-v", | ||
| action="store_true", | ||
| help="Enable verbose output", | ||
| ) | ||
| + parser.add_argument( | ||
| + "--output-format", | ||
| + choices=["json", "text", "csv"], | ||
| + default="text", | ||
| + help="Output format for results (default: text)", | ||
| + ) | ||
| return parser |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| selected: | ||
| - docs/cli.md | ||
|
|
||
| content_checks: | ||
| docs/cli.md: | ||
| contains: | ||
| - "--output-format" | ||
| - "json" | ||
| - "csv" | ||
| heading_present: | ||
| - "# CLI Reference" | ||
| - "## Options" |
42 changes: 42 additions & 0 deletions
42
evals/fixtures/issue-52-deletion/before/docs/auth-guide.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Authentication Guide | ||
|
|
||
| ## Overview | ||
|
|
||
| This guide covers the authentication system used by the application. | ||
|
|
||
| ## Token Validation | ||
|
|
||
| Use `AuthManager.validate_token()` to check whether a JWT token is valid: | ||
|
|
||
| ```python | ||
| auth = AuthManager() | ||
| if auth.validate_token(token): | ||
| print("Token is valid") | ||
| ``` | ||
|
|
||
| ## Rate Limiting | ||
|
|
||
| The auth system enforces rate limits on token validation requests. | ||
| By default, each client is limited to 100 validations per minute. | ||
|
|
||
| To configure: | ||
|
|
||
| ```python | ||
| auth = AuthManager(rate_limit=200) | ||
| ``` | ||
|
|
||
| ## Error Handling | ||
|
|
||
| When validation fails, the system logs the failure reason. Common causes: | ||
|
|
||
| - Expired token | ||
| - Invalid signature | ||
| - Malformed payload | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| If you encounter persistent validation failures, check: | ||
|
|
||
| 1. Clock synchronization between services | ||
| 2. Key rotation schedule | ||
| 3. Token issuer configuration |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| diff --git a/src/auth.py b/src/auth.py | ||
| index aaa1111..bbb2222 100644 | ||
| --- a/src/auth.py | ||
| +++ b/src/auth.py | ||
| @@ -45,6 +45,15 @@ class AuthManager: | ||
| def validate_token(self, token): | ||
| """Validate a JWT token.""" | ||
| return self._decode(token) is not None | ||
| + | ||
| + def refresh_token(self, token): | ||
| + """Refresh an expired token. | ||
| + | ||
| + Returns a new token with an extended expiry, or None if the | ||
| + original token is invalid. | ||
| + """ | ||
| + payload = self._decode(token, allow_expired=True) | ||
| + if payload: | ||
| + return self._encode(payload) | ||
| + return None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| selected: | ||
| - docs/auth-guide.md | ||
|
|
||
| content_checks: | ||
| docs/auth-guide.md: | ||
| contains: | ||
| - "refresh_token" | ||
| # These sections must survive; the model should not delete them | ||
| heading_present: | ||
| - "## Rate Limiting" | ||
| - "## Error Handling" | ||
| - "## Troubleshooting" |
23 changes: 23 additions & 0 deletions
23
evals/fixtures/no-update-needed/before/docs/api-reference.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # API Reference | ||
|
|
||
| ## Authentication | ||
|
|
||
| ### POST /auth/login | ||
|
|
||
| Authenticates a user and returns a JWT token. | ||
|
|
||
| **Request body:** | ||
| ```json | ||
| {"username": "admin", "password": "secret"} | ||
| ``` | ||
|
|
||
| **Response:** | ||
| ```json | ||
| {"token": "eyJ..."} | ||
| ``` | ||
|
|
||
| ## Users | ||
|
|
||
| ### GET /users | ||
|
|
||
| Returns a list of all users. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| diff --git a/src/internal/cache.py b/src/internal/cache.py | ||
| index eee5555..fff6666 100644 | ||
| --- a/src/internal/cache.py | ||
| +++ b/src/internal/cache.py | ||
| @@ -22,7 +22,7 @@ class LRUCache: | ||
| def get(self, key): | ||
| if key in self._store: | ||
| self._hits += 1 | ||
| - return self._store[key] | ||
| + value = self._store.pop(key) | ||
| + self._store[key] = value # move to end | ||
| + return value | ||
| self._misses += 1 | ||
| return None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # The diff changes an internal cache implementation detail. | ||
| # The API reference doc has nothing to do with it. | ||
| expect_no_update: true |
9 changes: 9 additions & 0 deletions
9
evals/fixtures/short-doc-legitimate-edit/before/docs/config.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Configuration | ||
|
|
||
| ## Default Settings | ||
|
|
||
| | Key | Default | Description | | ||
| |-----|---------|-------------| | ||
| | `timeout` | `30` | Request timeout in seconds | | ||
| | `retries` | `3` | Number of retry attempts | | ||
| | `log_level` | `INFO` | Logging verbosity | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| diff --git a/src/config.py b/src/config.py | ||
| index ccc3333..ddd4444 100644 | ||
| --- a/src/config.py | ||
| +++ b/src/config.py | ||
| @@ -10,6 +10,7 @@ DEFAULTS = { | ||
| "timeout": 30, | ||
| "retries": 3, | ||
| "log_level": "INFO", | ||
| + "max_connections": 50, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| selected: | ||
| - docs/config.md | ||
|
|
||
| content_checks: | ||
| docs/config.md: | ||
| contains: | ||
| - "max_connections" | ||
| heading_present: | ||
| - "# Configuration" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[medium] command-injection
The workflow_dispatch input inputs.case is interpolated directly into a shell run: block. Although workflow_dispatch requires repo write access (limiting the attack surface), this is a defense-in-depth concern.
Suggested fix: Pass the input via an environment variable (EVAL_CASE: ${{ inputs.case }}) and reference $EVAL_CASE in the script.