ragaudit-cli 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. ragaudit/__init__.py +1 -0
  2. ragaudit/adapters/__init__.py +0 -0
  3. ragaudit/adapters/base.py +109 -0
  4. ragaudit/adapters/callable_adapter.py +15 -0
  5. ragaudit/cli.py +540 -0
  6. ragaudit/cli_easy.py +458 -0
  7. ragaudit/cli_v2.py +507 -0
  8. ragaudit/env.py +46 -0
  9. ragaudit/generate/__init__.py +0 -0
  10. ragaudit/generate/pipeline.py +486 -0
  11. ragaudit/generate/prompts.py +116 -0
  12. ragaudit/generate/testset_generator.py +146 -0
  13. ragaudit/generate/text.py +75 -0
  14. ragaudit/ingest/__init__.py +0 -0
  15. ragaudit/ingest/chunker.py +53 -0
  16. ragaudit/ingest/corpus.py +120 -0
  17. ragaudit/ingest/loader.py +49 -0
  18. ragaudit/ingest/versioning.py +92 -0
  19. ragaudit/llm/__init__.py +0 -0
  20. ragaudit/llm/client.py +86 -0
  21. ragaudit/llm/factory.py +70 -0
  22. ragaudit/llm/json_output.py +72 -0
  23. ragaudit/llm/mock.py +552 -0
  24. ragaudit/models/__init__.py +0 -0
  25. ragaudit/models/adapter.py +39 -0
  26. ragaudit/models/document.py +28 -0
  27. ragaudit/models/evaluation.py +70 -0
  28. ragaudit/models/question.py +152 -0
  29. ragaudit/models/run.py +44 -0
  30. ragaudit/models/scoring.py +82 -0
  31. ragaudit/models/testset.py +30 -0
  32. ragaudit/models/tune.py +56 -0
  33. ragaudit/report/__init__.py +0 -0
  34. ragaudit/report/generator.py +155 -0
  35. ragaudit/report/plain_summary.py +122 -0
  36. ragaudit/report/templates/_v2_tooltip.jinja +43 -0
  37. ragaudit/report/templates/generate_report.html.jinja +175 -0
  38. ragaudit/report/templates/report.html.jinja +252 -0
  39. ragaudit/report/templates/style.css +1095 -0
  40. ragaudit/report/templates/tune_report.html.jinja +158 -0
  41. ragaudit/report/templates/v2.css +286 -0
  42. ragaudit/report/v2.py +380 -0
  43. ragaudit/runner/__init__.py +0 -0
  44. ragaudit/runner/ablation.py +46 -0
  45. ragaudit/runner/evaluate.py +325 -0
  46. ragaudit/runner/executor.py +98 -0
  47. ragaudit/runner/locked.py +37 -0
  48. ragaudit/runner/summary.py +45 -0
  49. ragaudit/scaffold.py +252 -0
  50. ragaudit/scoring/__init__.py +0 -0
  51. ragaudit/scoring/correctness.py +94 -0
  52. ragaudit/scoring/groundedness.py +86 -0
  53. ragaudit/scoring/retrieval.py +42 -0
  54. ragaudit/scoring/text_match.py +51 -0
  55. ragaudit/scoring/verdict.py +14 -0
  56. ragaudit/storage/__init__.py +0 -0
  57. ragaudit/storage/db.py +81 -0
  58. ragaudit/storage/lock.py +36 -0
  59. ragaudit/storage/repository.py +216 -0
  60. ragaudit/storage/store.py +536 -0
  61. ragaudit/tune/__init__.py +7 -0
  62. ragaudit/tune/devset.py +16 -0
  63. ragaudit/tune/loop.py +208 -0
  64. ragaudit/tune/search_space.py +140 -0
  65. ragaudit/tune/strategies.py +197 -0
  66. ragaudit/yaml_io.py +199 -0
  67. ragaudit_cli-0.3.0.dist-info/METADATA +429 -0
  68. ragaudit_cli-0.3.0.dist-info/RECORD +71 -0
  69. ragaudit_cli-0.3.0.dist-info/WHEEL +4 -0
  70. ragaudit_cli-0.3.0.dist-info/entry_points.txt +2 -0
  71. ragaudit_cli-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,158 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ragaudit tune report — {{ result.tune_id }}</title>
7
+ <style>
8
+ {{ style_css | safe }}
9
+ {{ v2_css | safe }}
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+
15
+ <header class="report-header">
16
+ <h1>ragaudit tune report{% if is_mock %}<span class="v2-badge-mock">built with the mock LLM</span>{% endif %}</h1>
17
+ <div class="meta">
18
+ Tune <code>{{ result.tune_id }}</code> ·
19
+ strategy <code>{{ result.strategy }}</code> ·
20
+ adapter <code>{{ result.adapter_path }}</code> ·
21
+ judge <code>{{ result.judge_model }}</code> ·
22
+ k={{ result.k }} ·
23
+ index <code>{{ result.index_version[:12] }}</code> ·
24
+ started {{ result.created_at }}
25
+ </div>
26
+ </header>
27
+
28
+ <section class="headline">
29
+ <h2>The headline finding</h2>
30
+ <p class="headline-sentence">
31
+ {% if best.index == baseline.index %}
32
+ No config beat the baseline on the dev set in {{ result.trials | length }} trial(s), so the baseline stays.
33
+ Its score on the locked test set is <strong>{{ "%.1f" | format(result.baseline_test.score) }}</strong>.
34
+ {% else %}
35
+ Tuning moved the honest score, measured once on the locked test set, from
36
+ <strong>{{ "%.1f" | format(result.baseline_test.score) }}</strong> to <strong>{{ "%.1f" | format(result.best_test.score) }}</strong>
37
+ ({{ "%+.1f" | format(improvement) }} points) after {{ result.trials | length }} trial(s).
38
+ The search only ever saw the dev set, where the same change was {{ "%+.1f" | format(dev_improvement) }} points.
39
+ {% endif %}
40
+ </p>
41
+ {% if is_mock %}
42
+ <div class="v2-callout warn">Built with ragaudit's offline mock LLM as both judge{% if result.strategy == "llm" %} and proposer{% endif %}. The numbers show how the loop behaves, not how good any real pipeline is.</div>
43
+ {% endif %}
44
+ <div class="stats-grid">
45
+ <div class="stat"><span class="value">{{ "%.1f" | format(result.baseline_test.score) }}</span><span class="label">Baseline, test</span></div>
46
+ <div class="stat"><span class="value">{{ "%.1f" | format(result.best_test.score) }}</span><span class="label">Best, test</span></div>
47
+ <div class="stat"><span class="value">{{ "%.1f" | format(best.dev.score) }}</span><span class="label">Best, dev</span></div>
48
+ <div class="stat"><span class="value">{{ "%+.1f" | format(result.overfit_gap) }}</span><span class="label">Dev minus test (best)</span></div>
49
+ </div>
50
+ <p class="meta">Score = share of questions where the gold passage was in the top {{ result.k }} <em>and</em> the answer was judged correct, ×100.
51
+ Dev: {{ best.dev.n_questions }} questions. Test: {{ result.best_test.n_questions }} questions.</p>
52
+ </section>
53
+
54
+ <section>
55
+ <h2>Overfitting check</h2>
56
+ {% if result.overfit_warning %}
57
+ <div class="v2-callout warn">
58
+ <strong>Likely overfit to the dev set.</strong> The best config scores {{ "%.1f" | format(best.dev.score) }} on dev but
59
+ {{ "%.1f" | format(result.best_test.score) }} on the locked test set, a gap of {{ "%.1f" | format(result.overfit_gap) }} points,
60
+ more than the {{ "%.1f" | format(result.overfit_threshold) }}-point threshold. Treat the dev score as optimistic,
61
+ and consider generating more questions before trusting this config.
62
+ </div>
63
+ {% else %}
64
+ <div class="v2-callout ok">
65
+ <strong>No sign of overfitting.</strong> The best config's dev score ({{ "%.1f" | format(best.dev.score) }}) is within
66
+ {{ "%.1f" | format(result.overfit_threshold) }} points of its locked test score ({{ "%.1f" | format(result.best_test.score) }});
67
+ the gap is {{ "%+.1f" | format(result.overfit_gap) }}.
68
+ </div>
69
+ {% endif %}
70
+ <figure class="v2-figure">{{ dev_test_chart | safe }}</figure>
71
+ <ul class="v2-legend">
72
+ <li><span class="v2-swatch" style="background: var(--v2-dev)"></span>Dev (searched on)</li>
73
+ <li><span class="v2-swatch" style="background: var(--v2-test)"></span>Locked test (scored once at the end)</li>
74
+ </ul>
75
+ </section>
76
+
77
+ <section>
78
+ <h2>Dev score per trial</h2>
79
+ <p>Each dot is one config evaluated on the dev set. The dashed line is the best dev score so far; the white dot is the config that was kept.
80
+ Trials by source: {% for source, count in sources.items() %}{{ count }} {{ source }}{% if not loop.last %}, {% endif %}{% endfor %}.
81
+ Stopped because: <code>{{ result.stopped_reason }}</code>. LLM calls used: {{ result.llm_calls }}.</p>
82
+ <figure class="v2-figure">{{ trial_chart | safe }}</figure>
83
+ <ul class="v2-legend">
84
+ <li><span class="v2-swatch dot" style="background: var(--v2-dev)"></span>Trial dev score</li>
85
+ <li><span class="v2-swatch dot" style="background: var(--text)"></span>Best (kept)</li>
86
+ <li><span class="v2-swatch line"></span>Best so far</li>
87
+ </ul>
88
+ <details class="v2-data">
89
+ <summary>Show every trial as a table</summary>
90
+ <div class="v2-table-wrap">
91
+ <table class="v2-table">
92
+ <thead><tr><th class="num">#</th><th>Source</th><th class="num">Dev score</th><th class="num">Pass</th><th class="num">Gen miss</th><th class="num">Ret miss</th><th>Changed from baseline</th><th>Rationale</th></tr></thead>
93
+ <tbody>
94
+ {% for t in trial_rows %}
95
+ <tr{% if t.is_best %} class="is-best"{% endif %}>
96
+ <td class="num">{{ t.index }}</td>
97
+ <td>{{ t.source }}{% if t.is_best %} (best){% endif %}</td>
98
+ <td class="num">{{ "%.1f" | format(t.score) }}</td>
99
+ <td class="num">{{ t.counts.get("pass", 0) }}</td>
100
+ <td class="num">{{ t.counts.get("generation_miss", 0) }}</td>
101
+ <td class="num">{{ t.counts.get("retrieval_miss", 0) }}</td>
102
+ <td><code>{{ t.changed }}</code></td>
103
+ <td>{{ t.rationale }}</td>
104
+ </tr>
105
+ {% endfor %}
106
+ </tbody>
107
+ </table>
108
+ </div>
109
+ </details>
110
+ </section>
111
+
112
+ <section>
113
+ <h2>Failure classes before and after</h2>
114
+ <p><strong>Retrieval miss</strong>: the gold passage wasn't in the top {{ result.k }} (a correct answer without it counts here too, since it was luck).
115
+ <strong>Generation miss</strong>: the passage was retrieved but the answer was still wrong.</p>
116
+ <figure class="v2-figure">{{ failure_chart | safe }}</figure>
117
+ <ul class="v2-legend">
118
+ {% for key, name, cls in failure_classes %}
119
+ <li><span class="v2-swatch" style="background: var(--{{ {'pass': 'true-pass', 'generation_miss': 'generation-failure', 'retrieval_miss': 'retrieval-failure'}[key] }})"></span>{{ name }}</li>
120
+ {% endfor %}
121
+ </ul>
122
+ <details class="v2-data">
123
+ <summary>Show as a table</summary>
124
+ <table class="v2-table">
125
+ <thead><tr><th>Config, split</th>{% for key, name, cls in failure_classes %}<th class="num">{{ name }}</th>{% endfor %}</tr></thead>
126
+ <tbody>
127
+ {% for label, counts in stack_rows %}
128
+ <tr><td>{{ label }}</td>{% for key, name, cls in failure_classes %}<td class="num">{{ counts.get(key, 0) }}</td>{% endfor %}</tr>
129
+ {% endfor %}
130
+ </tbody>
131
+ </table>
132
+ </details>
133
+ </section>
134
+
135
+ <section>
136
+ <h2>Best config</h2>
137
+ {% if result.best_config_path %}<p>Written to <code>{{ result.best_config_path }}</code>.</p>{% endif %}
138
+ <div class="v2-table-wrap">
139
+ <table class="v2-table">
140
+ <thead><tr><th>Knob</th><th>Baseline</th><th>Best</th><th>Searched over</th></tr></thead>
141
+ <tbody>
142
+ {% for k in knobs %}
143
+ <tr>
144
+ <td><code>{{ k.knob }}</code></td>
145
+ <td>{{ k.baseline }}</td>
146
+ <td class="{{ 'v2-changed' if k.changed else 'v2-unchanged' }}">{{ k.best }}{% if k.changed %} (changed){% endif %}</td>
147
+ <td class="v2-unchanged">{{ result.search_space[k.knob] | join(", ") }}</td>
148
+ </tr>
149
+ {% endfor %}
150
+ </tbody>
151
+ </table>
152
+ </div>
153
+ </section>
154
+
155
+ </div>
156
+ {% include "_v2_tooltip.jinja" %}
157
+ </body>
158
+ </html>
@@ -0,0 +1,286 @@
1
+ /* v2-only additions for the generate and tune reports. Loaded after
2
+ style.css, which stays the single source for the shared look. */
3
+
4
+ :root {
5
+ --v2-dev: #3987e5;
6
+ --v2-test: #d95926;
7
+ --v2-muted-mark: #4a5160;
8
+ --v2-grid: #262b35;
9
+ }
10
+
11
+ .v2-badge-mock {
12
+ display: inline-block;
13
+ margin-left: 0.5rem;
14
+ padding: 0.1rem 0.5rem;
15
+ border: 1px solid var(--lucky-pass);
16
+ border-radius: 999px;
17
+ color: var(--lucky-pass);
18
+ font-size: 0.75rem;
19
+ vertical-align: middle;
20
+ }
21
+
22
+ .v2-callout {
23
+ margin: 1rem 0;
24
+ padding: 0.75rem 1rem;
25
+ border-left: 3px solid var(--accent);
26
+ background: var(--surface-raised);
27
+ border-radius: 4px;
28
+ }
29
+
30
+ .v2-callout.warn {
31
+ border-left-color: var(--lucky-pass);
32
+ }
33
+
34
+ .v2-callout.ok {
35
+ border-left-color: var(--true-pass);
36
+ }
37
+
38
+ .v2-figure {
39
+ position: relative;
40
+ margin: 1rem 0 0.5rem;
41
+ }
42
+
43
+ .v2-chart {
44
+ display: block;
45
+ width: 100%;
46
+ height: auto;
47
+ overflow: visible;
48
+ }
49
+
50
+ .v2-grid {
51
+ stroke: var(--v2-grid);
52
+ stroke-width: 1;
53
+ }
54
+
55
+ .v2-tick,
56
+ .v2-axis-title {
57
+ fill: var(--text-muted);
58
+ font-size: 12px;
59
+ }
60
+
61
+ .v2-label {
62
+ fill: var(--text);
63
+ font-size: 13px;
64
+ }
65
+
66
+ .v2-value {
67
+ fill: var(--text-muted);
68
+ font-size: 12px;
69
+ font-variant-numeric: tabular-nums;
70
+ }
71
+
72
+ .v2-inside {
73
+ fill: #0f1115;
74
+ font-size: 12px;
75
+ font-weight: 600;
76
+ pointer-events: none;
77
+ }
78
+
79
+ .v2-rule {
80
+ stroke: var(--text);
81
+ stroke-width: 1.5;
82
+ stroke-dasharray: 4 3;
83
+ }
84
+
85
+ .v2-mark {
86
+ outline: none;
87
+ }
88
+
89
+ .v2-mark:hover,
90
+ .v2-mark:focus-visible {
91
+ filter: brightness(1.2);
92
+ }
93
+
94
+ .v2-fill-accent { fill: var(--accent); }
95
+ .v2-fill-muted { fill: var(--v2-muted-mark); }
96
+ .v2-fill-dev { fill: var(--v2-dev); }
97
+ .v2-fill-test { fill: var(--v2-test); }
98
+ .v2-fill-pass { fill: var(--true-pass); }
99
+ .v2-fill-gen { fill: var(--generation-failure); }
100
+ .v2-fill-ret { fill: var(--retrieval-failure); }
101
+
102
+ .v2-dot {
103
+ fill: var(--v2-dev);
104
+ stroke: var(--surface);
105
+ stroke-width: 2;
106
+ pointer-events: none;
107
+ }
108
+
109
+ .v2-dot-best {
110
+ fill: var(--text);
111
+ }
112
+
113
+ .v2-hit {
114
+ fill: transparent;
115
+ outline: none;
116
+ cursor: default;
117
+ }
118
+
119
+ .v2-hit:focus-visible {
120
+ stroke: var(--text);
121
+ stroke-width: 1;
122
+ }
123
+
124
+ .v2-line-ref {
125
+ fill: none;
126
+ stroke: var(--text-muted);
127
+ stroke-width: 2;
128
+ stroke-dasharray: 2 4;
129
+ }
130
+
131
+ .v2-legend {
132
+ display: flex;
133
+ flex-wrap: wrap;
134
+ gap: 0.25rem 1.25rem;
135
+ margin: 0.25rem 0 0;
136
+ padding: 0;
137
+ list-style: none;
138
+ font-size: 0.85rem;
139
+ color: var(--text-muted);
140
+ }
141
+
142
+ .v2-legend li {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 0.4rem;
146
+ }
147
+
148
+ .v2-swatch {
149
+ display: inline-block;
150
+ width: 12px;
151
+ height: 12px;
152
+ border-radius: 3px;
153
+ }
154
+
155
+ .v2-swatch.line {
156
+ width: 18px;
157
+ height: 0;
158
+ border-top: 2px dashed var(--text-muted);
159
+ border-radius: 0;
160
+ }
161
+
162
+ .v2-swatch.dot {
163
+ border-radius: 50%;
164
+ }
165
+
166
+ .v2-tooltip {
167
+ position: absolute;
168
+ z-index: 10;
169
+ max-width: 280px;
170
+ padding: 0.45rem 0.6rem;
171
+ background: var(--surface-raised);
172
+ border: 1px solid var(--border);
173
+ border-radius: 6px;
174
+ color: var(--text-muted);
175
+ font-size: 0.8rem;
176
+ line-height: 1.35;
177
+ pointer-events: none;
178
+ white-space: pre-line;
179
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
180
+ }
181
+
182
+ .v2-tooltip strong {
183
+ display: block;
184
+ color: var(--text);
185
+ font-size: 0.95rem;
186
+ }
187
+
188
+ .v2-table-wrap {
189
+ overflow-x: auto;
190
+ }
191
+
192
+ table.v2-table {
193
+ width: 100%;
194
+ border-collapse: collapse;
195
+ font-size: 0.9rem;
196
+ }
197
+
198
+ table.v2-table th,
199
+ table.v2-table td {
200
+ padding: 0.45rem 0.6rem;
201
+ border-bottom: 1px solid var(--border);
202
+ text-align: left;
203
+ vertical-align: top;
204
+ }
205
+
206
+ table.v2-table th {
207
+ color: var(--text-muted);
208
+ font-weight: 600;
209
+ }
210
+
211
+ table.v2-table td.num,
212
+ table.v2-table th.num {
213
+ text-align: right;
214
+ font-variant-numeric: tabular-nums;
215
+ }
216
+
217
+ table.v2-table tr.is-best td {
218
+ background: var(--surface-raised);
219
+ }
220
+
221
+ .v2-changed {
222
+ color: var(--text);
223
+ font-weight: 600;
224
+ }
225
+
226
+ .v2-unchanged {
227
+ color: var(--text-muted);
228
+ }
229
+
230
+ details.v2-data {
231
+ margin-top: 0.5rem;
232
+ }
233
+
234
+ details.v2-data summary {
235
+ color: var(--text-muted);
236
+ font-size: 0.85rem;
237
+ }
238
+
239
+ .v2-q {
240
+ margin: 0;
241
+ padding: 0.75rem 0;
242
+ border-bottom: 1px solid var(--border);
243
+ }
244
+
245
+ .v2-q:last-child {
246
+ border-bottom: none;
247
+ }
248
+
249
+ .v2-q .q {
250
+ color: var(--text);
251
+ font-weight: 600;
252
+ }
253
+
254
+ .v2-q .a,
255
+ .v2-q .src {
256
+ color: var(--text-muted);
257
+ font-size: 0.875rem;
258
+ }
259
+
260
+ .v2-tag {
261
+ display: inline-block;
262
+ margin-right: 0.4rem;
263
+ padding: 0 0.4rem;
264
+ border: 1px solid var(--border);
265
+ border-radius: 4px;
266
+ color: var(--text-muted);
267
+ font-size: 0.75rem;
268
+ }
269
+
270
+ .meta code {
271
+ overflow-wrap: anywhere;
272
+ }
273
+
274
+ @media (max-width: 600px) {
275
+ .container {
276
+ padding: 1.25rem 1rem 3rem;
277
+ }
278
+
279
+ section {
280
+ padding: 1rem;
281
+ }
282
+
283
+ .stats-grid {
284
+ grid-template-columns: repeat(2, minmax(0, 1fr));
285
+ }
286
+ }