-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathaction.yml
More file actions
303 lines (269 loc) · 10.2 KB
/
Copy pathaction.yml
File metadata and controls
303 lines (269 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
name: 'EvalView - AI Agent Testing'
description: 'Catch agent regressions in CI. Detects tool changes, output drift, cost spikes. Works with LangGraph, CrewAI, Claude, GPT.'
author: 'EvalView Team'
branding:
icon: 'check-circle'
color: 'purple'
inputs:
mode:
description: 'Testing mode: "check" for regression detection (default), "run" for full evaluation'
required: false
default: 'check'
command:
description: 'Alias for "mode" — accepts "check" or "run". If both are set, "command" takes precedence.'
required: false
openai-api-key:
description: 'OpenAI API key for LLM-as-judge evaluation'
required: false
anthropic-api-key:
description: 'Anthropic API key (optional alternative to OpenAI)'
required: false
test-path:
description: 'Path to test cases directory'
required: false
default: 'tests'
config-path:
description: 'Path to EvalView config file'
required: false
default: '.evalview/config.yaml'
filter:
description: 'Filter tests by name pattern'
required: false
max-workers:
description: 'Maximum parallel workers'
required: false
default: '4'
max-retries:
description: 'Maximum retries for failed tests'
required: false
default: '2'
fail-on-error:
description: 'Fail the workflow if any test fails or regressions detected'
required: false
default: 'true'
fail-on:
description: 'Comma-separated statuses that fail CI (REGRESSION, TOOLS_CHANGED, OUTPUT_CHANGED, CONTRACT_DRIFT)'
required: false
default: 'REGRESSION'
strict:
description: 'Fail on any change (REGRESSION, TOOLS_CHANGED, OUTPUT_CHANGED)'
required: false
default: 'false'
diff:
description: 'Compare against golden baselines in "run" mode (ignored in "check" mode which always diffs)'
required: false
default: 'false'
contracts:
description: 'Check MCP contracts for interface drift before running tests'
required: false
default: 'false'
generate-report:
description: 'Generate HTML report'
required: false
default: 'true'
post-comment:
description: 'Automatically post results as PR comment (requires GITHUB_TOKEN)'
required: false
default: 'true'
upload-artifact:
description: 'Upload results and report as workflow artifacts'
required: false
default: 'true'
evalview-version:
description: 'EvalView version to install (e.g. "0.5.3", default: latest)'
required: false
default: ''
python-version:
description: 'Python version to use'
required: false
default: '3.11'
outputs:
results-file:
description: 'Path to JSON results file'
value: ${{ steps.run-tests.outputs.results-file }}
report-file:
description: 'Path to HTML report file'
value: ${{ steps.run-tests.outputs.report-file }}
total-tests:
description: 'Total number of tests run'
value: ${{ steps.run-tests.outputs.total-tests }}
passed-tests:
description: 'Number of passed/unchanged tests'
value: ${{ steps.run-tests.outputs.passed-tests }}
failed-tests:
description: 'Number of failed/regressed tests'
value: ${{ steps.run-tests.outputs.failed-tests }}
regressions:
description: 'Number of regressions detected (check mode)'
value: ${{ steps.run-tests.outputs.regressions }}
tools-changed:
description: 'Number of tests with tool changes (check mode)'
value: ${{ steps.run-tests.outputs.tools-changed }}
pass-rate:
description: 'Pass rate percentage'
value: ${{ steps.run-tests.outputs.pass-rate }}
status:
description: 'Overall status: passed, regression, tools_changed, output_changed'
value: ${{ steps.run-tests.outputs.status }}
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Install EvalView
shell: bash
run: |
python -m pip install --upgrade pip
if [ -n "${{ inputs.evalview-version }}" ]; then
pip install "evalview==${{ inputs.evalview-version }}"
else
pip install evalview
fi
- name: Run EvalView Tests
id: run-tests
shell: bash
env:
OPENAI_API_KEY: ${{ inputs.openai-api-key }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
INPUT_MODE: ${{ inputs.command || inputs.mode }}
INPUT_TEST_PATH: ${{ inputs.test-path }}
INPUT_FAIL_ON: ${{ inputs.fail-on }}
INPUT_STRICT: ${{ inputs.strict }}
INPUT_FILTER: ${{ inputs.filter }}
INPUT_MAX_WORKERS: ${{ inputs.max-workers }}
INPUT_MAX_RETRIES: ${{ inputs.max-retries }}
INPUT_DIFF: ${{ inputs.diff }}
INPUT_CONTRACTS: ${{ inputs.contracts }}
INPUT_GENERATE_REPORT: ${{ inputs.generate-report }}
INPUT_FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
run: |
REPORT_PATH=""
if [ "$INPUT_GENERATE_REPORT" = "true" ]; then
REPORT_PATH="evalview-report.html"
fi
if [ "$INPUT_MODE" = "check" ]; then
# ── Check mode (default): regression detection against baselines ──
ARGS=("$INPUT_TEST_PATH" "--json" "--fail-on" "$INPUT_FAIL_ON")
if [ "$INPUT_STRICT" = "true" ]; then
ARGS+=("--strict")
fi
if [ -n "$REPORT_PATH" ]; then
ARGS+=("--report" "$REPORT_PATH")
fi
if [ -n "$INPUT_FILTER" ]; then
ARGS+=("--test" "$INPUT_FILTER")
fi
# Run check — capture JSON on stdout, let stderr pass through
RESULTS_FILE=".evalview/results/check-ci.json"
mkdir -p .evalview/results
set +e
evalview check "${ARGS[@]}" > "$RESULTS_FILE"
EXIT_CODE=$?
set -e
echo "results-file=$RESULTS_FILE" >> "$GITHUB_OUTPUT"
# Parse check JSON output
TOTAL=$(jq -r '.summary.total_tests // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
UNCHANGED=$(jq -r '.summary.unchanged // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
REGRESSIONS=$(jq -r '.summary.regressions // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
TOOLS_CHANGED=$(jq -r '.summary.tools_changed // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
OUTPUT_CHANGED=$(jq -r '.summary.output_changed // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
FAILED=$((REGRESSIONS + TOOLS_CHANGED + OUTPUT_CHANGED))
if [ "$TOTAL" -gt 0 ]; then
PASS_RATE=$(echo "scale=1; $UNCHANGED * 100 / $TOTAL" | bc)
else
PASS_RATE="0"
fi
# Determine overall status
if [ "$REGRESSIONS" -gt 0 ]; then
STATUS="regression"
elif [ "$TOOLS_CHANGED" -gt 0 ]; then
STATUS="tools_changed"
elif [ "$OUTPUT_CHANGED" -gt 0 ]; then
STATUS="output_changed"
else
STATUS="passed"
fi
echo "total-tests=$TOTAL" >> "$GITHUB_OUTPUT"
echo "passed-tests=$UNCHANGED" >> "$GITHUB_OUTPUT"
echo "failed-tests=$FAILED" >> "$GITHUB_OUTPUT"
echo "regressions=$REGRESSIONS" >> "$GITHUB_OUTPUT"
echo "tools-changed=$TOOLS_CHANGED" >> "$GITHUB_OUTPUT"
echo "pass-rate=$PASS_RATE" >> "$GITHUB_OUTPUT"
echo "status=$STATUS" >> "$GITHUB_OUTPUT"
else
# ── Run mode: full evaluation with scoring ──
ARGS=("--max-workers" "$INPUT_MAX_WORKERS" "--max-retries" "$INPUT_MAX_RETRIES")
if [ -n "$INPUT_FILTER" ]; then
ARGS+=("--filter" "$INPUT_FILTER")
fi
if [ "$INPUT_DIFF" = "true" ]; then
ARGS+=("--diff" "--fail-on" "$INPUT_FAIL_ON")
fi
if [ "$INPUT_CONTRACTS" = "true" ]; then
ARGS+=("--contracts")
fi
if [ -n "$REPORT_PATH" ]; then
ARGS+=("--html-report" "$REPORT_PATH")
fi
set +e
evalview run "${ARGS[@]}"
EXIT_CODE=$?
set -e
# Find results file
RESULTS_FILE=$(ls -t .evalview/results/*.json 2>/dev/null | head -1)
if [ -n "$RESULTS_FILE" ]; then
echo "results-file=$RESULTS_FILE" >> "$GITHUB_OUTPUT"
TOTAL=$(jq '.summary.total // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
PASSED=$(jq '.summary.passed // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
FAILED=$(jq '.summary.failed // 0' "$RESULTS_FILE" 2>/dev/null || echo "0")
if [ "$TOTAL" -gt 0 ]; then
PASS_RATE=$(echo "scale=1; $PASSED * 100 / $TOTAL" | bc)
else
PASS_RATE="0"
fi
# Derive status from results
if [ "$FAILED" -gt 0 ]; then
STATUS="regression"
else
STATUS="passed"
fi
echo "total-tests=$TOTAL" >> "$GITHUB_OUTPUT"
echo "passed-tests=$PASSED" >> "$GITHUB_OUTPUT"
echo "failed-tests=$FAILED" >> "$GITHUB_OUTPUT"
echo "pass-rate=$PASS_RATE" >> "$GITHUB_OUTPUT"
echo "status=$STATUS" >> "$GITHUB_OUTPUT"
fi
fi
if [ -f "$REPORT_PATH" ]; then
echo "report-file=$REPORT_PATH" >> "$GITHUB_OUTPUT"
fi
# Save exit code for later — comment/summary steps run first
echo "exit-code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
- name: Post PR comment & job summary
if: always() && inputs.post-comment == 'true'
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
# Post comment (updates existing or creates new; gracefully no-ops outside PRs)
evalview ci comment 2>/dev/null || true
- name: Upload artifacts
if: always() && inputs.upload-artifact == 'true'
uses: actions/upload-artifact@v4
with:
name: evalview-results
path: |
.evalview/results/*.json
evalview-report.html
retention-days: 30
if-no-files-found: ignore
- name: Exit with test result
if: always()
shell: bash
run: |
EXIT_CODE="${{ steps.run-tests.outputs.exit-code }}"
if [ "${{ inputs.fail-on-error }}" = "true" ] && [ "${EXIT_CODE:-0}" -ne 0 ]; then
exit ${EXIT_CODE}
fi