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.
- ragaudit/__init__.py +1 -0
- ragaudit/adapters/__init__.py +0 -0
- ragaudit/adapters/base.py +109 -0
- ragaudit/adapters/callable_adapter.py +15 -0
- ragaudit/cli.py +540 -0
- ragaudit/cli_easy.py +458 -0
- ragaudit/cli_v2.py +507 -0
- ragaudit/env.py +46 -0
- ragaudit/generate/__init__.py +0 -0
- ragaudit/generate/pipeline.py +486 -0
- ragaudit/generate/prompts.py +116 -0
- ragaudit/generate/testset_generator.py +146 -0
- ragaudit/generate/text.py +75 -0
- ragaudit/ingest/__init__.py +0 -0
- ragaudit/ingest/chunker.py +53 -0
- ragaudit/ingest/corpus.py +120 -0
- ragaudit/ingest/loader.py +49 -0
- ragaudit/ingest/versioning.py +92 -0
- ragaudit/llm/__init__.py +0 -0
- ragaudit/llm/client.py +86 -0
- ragaudit/llm/factory.py +70 -0
- ragaudit/llm/json_output.py +72 -0
- ragaudit/llm/mock.py +552 -0
- ragaudit/models/__init__.py +0 -0
- ragaudit/models/adapter.py +39 -0
- ragaudit/models/document.py +28 -0
- ragaudit/models/evaluation.py +70 -0
- ragaudit/models/question.py +152 -0
- ragaudit/models/run.py +44 -0
- ragaudit/models/scoring.py +82 -0
- ragaudit/models/testset.py +30 -0
- ragaudit/models/tune.py +56 -0
- ragaudit/report/__init__.py +0 -0
- ragaudit/report/generator.py +155 -0
- ragaudit/report/plain_summary.py +122 -0
- ragaudit/report/templates/_v2_tooltip.jinja +43 -0
- ragaudit/report/templates/generate_report.html.jinja +175 -0
- ragaudit/report/templates/report.html.jinja +252 -0
- ragaudit/report/templates/style.css +1095 -0
- ragaudit/report/templates/tune_report.html.jinja +158 -0
- ragaudit/report/templates/v2.css +286 -0
- ragaudit/report/v2.py +380 -0
- ragaudit/runner/__init__.py +0 -0
- ragaudit/runner/ablation.py +46 -0
- ragaudit/runner/evaluate.py +325 -0
- ragaudit/runner/executor.py +98 -0
- ragaudit/runner/locked.py +37 -0
- ragaudit/runner/summary.py +45 -0
- ragaudit/scaffold.py +252 -0
- ragaudit/scoring/__init__.py +0 -0
- ragaudit/scoring/correctness.py +94 -0
- ragaudit/scoring/groundedness.py +86 -0
- ragaudit/scoring/retrieval.py +42 -0
- ragaudit/scoring/text_match.py +51 -0
- ragaudit/scoring/verdict.py +14 -0
- ragaudit/storage/__init__.py +0 -0
- ragaudit/storage/db.py +81 -0
- ragaudit/storage/lock.py +36 -0
- ragaudit/storage/repository.py +216 -0
- ragaudit/storage/store.py +536 -0
- ragaudit/tune/__init__.py +7 -0
- ragaudit/tune/devset.py +16 -0
- ragaudit/tune/loop.py +208 -0
- ragaudit/tune/search_space.py +140 -0
- ragaudit/tune/strategies.py +197 -0
- ragaudit/yaml_io.py +199 -0
- ragaudit_cli-0.3.0.dist-info/METADATA +429 -0
- ragaudit_cli-0.3.0.dist-info/RECORD +71 -0
- ragaudit_cli-0.3.0.dist-info/WHEEL +4 -0
- ragaudit_cli-0.3.0.dist-info/entry_points.txt +2 -0
- ragaudit_cli-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Lossless save/load functions for TestSet and Run.
|
|
2
|
+
|
|
3
|
+
Simple flat fields get their own columns; nested scoring structures
|
|
4
|
+
(ScoredCase, RunSummary) are stored as JSON via Pydantic's own
|
|
5
|
+
model_dump_json/model_validate_json, so round-tripping is exactly as
|
|
6
|
+
lossless as Pydantic's own serialization — no hand-rolled re-encoding
|
|
7
|
+
of nested objects.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sqlite3
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
from ragaudit.models.document import Chunk, Document
|
|
15
|
+
from ragaudit.models.run import Run, RunConfig, RunSummary
|
|
16
|
+
from ragaudit.models.scoring import ScoredCase
|
|
17
|
+
from ragaudit.models.testset import TestCase, TestSet
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def save_documents(conn: sqlite3.Connection, documents: list[Document]) -> None:
|
|
21
|
+
"""Replace the full set of ingested documents with `documents`."""
|
|
22
|
+
conn.execute("DELETE FROM documents")
|
|
23
|
+
for document in documents:
|
|
24
|
+
conn.execute(
|
|
25
|
+
"INSERT INTO documents (doc_id, source_path, content, content_hash, metadata_json) VALUES (?, ?, ?, ?, ?)",
|
|
26
|
+
(document.doc_id, document.source_path, document.content, document.content_hash, json.dumps(document.metadata)),
|
|
27
|
+
)
|
|
28
|
+
conn.commit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_documents(conn: sqlite3.Connection) -> list[Document]:
|
|
32
|
+
"""Load every ingested Document."""
|
|
33
|
+
rows = conn.execute("SELECT doc_id, source_path, content, content_hash, metadata_json FROM documents").fetchall()
|
|
34
|
+
return [
|
|
35
|
+
Document(doc_id=r[0], source_path=r[1], content=r[2], content_hash=r[3], metadata=json.loads(r[4]))
|
|
36
|
+
for r in rows
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_chunks(conn: sqlite3.Connection, chunks: list[Chunk]) -> None:
|
|
41
|
+
"""Replace the full set of ingested chunks with `chunks`."""
|
|
42
|
+
conn.execute("DELETE FROM chunks")
|
|
43
|
+
for chunk in chunks:
|
|
44
|
+
conn.execute(
|
|
45
|
+
"INSERT INTO chunks (chunk_id, doc_id, position, text, token_count, metadata_json) VALUES (?, ?, ?, ?, ?, ?)",
|
|
46
|
+
(chunk.chunk_id, chunk.doc_id, chunk.position, chunk.text, chunk.token_count, json.dumps(chunk.metadata)),
|
|
47
|
+
)
|
|
48
|
+
conn.commit()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_chunks(conn: sqlite3.Connection) -> list[Chunk]:
|
|
52
|
+
"""Load every ingested Chunk, ordered by document then position."""
|
|
53
|
+
rows = conn.execute(
|
|
54
|
+
"SELECT chunk_id, doc_id, position, text, token_count, metadata_json FROM chunks ORDER BY doc_id, position"
|
|
55
|
+
).fetchall()
|
|
56
|
+
return [
|
|
57
|
+
Chunk(chunk_id=r[0], doc_id=r[1], position=r[2], text=r[3], token_count=r[4], metadata=json.loads(r[5]))
|
|
58
|
+
for r in rows
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_latest_testset_id(conn: sqlite3.Connection) -> str | None:
|
|
63
|
+
"""Return the testset_id of the most recently created TestSet, or None if there is none."""
|
|
64
|
+
row = conn.execute("SELECT testset_id FROM testsets ORDER BY created_at DESC LIMIT 1").fetchone()
|
|
65
|
+
return row[0] if row else None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_latest_run_id(conn: sqlite3.Connection) -> str | None:
|
|
69
|
+
"""Return the run_id of the most recently started Run, or None if there is none."""
|
|
70
|
+
row = conn.execute("SELECT run_id FROM runs ORDER BY started_at DESC LIMIT 1").fetchone()
|
|
71
|
+
return row[0] if row else None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_test_set(conn: sqlite3.Connection, test_set: TestSet) -> None:
|
|
75
|
+
"""Persist a TestSet and its TestCases, replacing any existing rows with the same testset_id."""
|
|
76
|
+
conn.execute("DELETE FROM test_cases WHERE testset_id = ?", (test_set.testset_id,))
|
|
77
|
+
conn.execute("DELETE FROM testsets WHERE testset_id = ?", (test_set.testset_id,))
|
|
78
|
+
|
|
79
|
+
conn.execute(
|
|
80
|
+
"INSERT INTO testsets (testset_id, corpus_hash, created_at, generation_model) VALUES (?, ?, ?, ?)",
|
|
81
|
+
(test_set.testset_id, test_set.corpus_hash, test_set.created_at.isoformat(), test_set.generation_model),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
for position, test_case in enumerate(test_set.test_cases):
|
|
85
|
+
conn.execute(
|
|
86
|
+
"""
|
|
87
|
+
INSERT INTO test_cases (
|
|
88
|
+
test_id, testset_id, position, question, expected_answer,
|
|
89
|
+
source_chunk_id, source_doc_id, generation_model, created_at, metadata_json
|
|
90
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
91
|
+
""",
|
|
92
|
+
(
|
|
93
|
+
test_case.test_id,
|
|
94
|
+
test_set.testset_id,
|
|
95
|
+
position,
|
|
96
|
+
test_case.question,
|
|
97
|
+
test_case.expected_answer,
|
|
98
|
+
test_case.source_chunk_id,
|
|
99
|
+
test_case.source_doc_id,
|
|
100
|
+
test_case.generation_model,
|
|
101
|
+
test_case.created_at.isoformat(),
|
|
102
|
+
json.dumps(test_case.metadata),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
conn.commit()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def load_test_set(conn: sqlite3.Connection, testset_id: str) -> TestSet | None:
|
|
110
|
+
"""Load a TestSet by id, with its TestCases in their original order. None if not found."""
|
|
111
|
+
row = conn.execute(
|
|
112
|
+
"SELECT testset_id, corpus_hash, created_at, generation_model FROM testsets WHERE testset_id = ?",
|
|
113
|
+
(testset_id,),
|
|
114
|
+
).fetchone()
|
|
115
|
+
if row is None:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
case_rows = conn.execute(
|
|
119
|
+
"""
|
|
120
|
+
SELECT test_id, question, expected_answer, source_chunk_id, source_doc_id,
|
|
121
|
+
generation_model, created_at, metadata_json
|
|
122
|
+
FROM test_cases WHERE testset_id = ? ORDER BY position ASC
|
|
123
|
+
""",
|
|
124
|
+
(testset_id,),
|
|
125
|
+
).fetchall()
|
|
126
|
+
|
|
127
|
+
test_cases = [
|
|
128
|
+
TestCase(
|
|
129
|
+
test_id=r[0],
|
|
130
|
+
question=r[1],
|
|
131
|
+
expected_answer=r[2],
|
|
132
|
+
source_chunk_id=r[3],
|
|
133
|
+
source_doc_id=r[4],
|
|
134
|
+
generation_model=r[5],
|
|
135
|
+
created_at=datetime.fromisoformat(r[6]),
|
|
136
|
+
metadata=json.loads(r[7]),
|
|
137
|
+
)
|
|
138
|
+
for r in case_rows
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
return TestSet(
|
|
142
|
+
testset_id=row[0],
|
|
143
|
+
corpus_hash=row[1],
|
|
144
|
+
created_at=datetime.fromisoformat(row[2]),
|
|
145
|
+
generation_model=row[3],
|
|
146
|
+
test_cases=test_cases,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def save_run(conn: sqlite3.Connection, run: Run) -> None:
|
|
151
|
+
"""Persist a Run: config, scored cases, and summary. Replaces any existing rows with the same run_id."""
|
|
152
|
+
run_id = run.config.run_id
|
|
153
|
+
conn.execute("DELETE FROM scored_cases WHERE run_id = ?", (run_id,))
|
|
154
|
+
conn.execute("DELETE FROM runs WHERE run_id = ?", (run_id,))
|
|
155
|
+
|
|
156
|
+
conn.execute(
|
|
157
|
+
"""
|
|
158
|
+
INSERT INTO runs (
|
|
159
|
+
run_id, testset_id, adapter_path, correctness_judge_model,
|
|
160
|
+
groundedness_judge_model, run_ablation, started_at, finished_at, summary_json
|
|
161
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
162
|
+
""",
|
|
163
|
+
(
|
|
164
|
+
run_id,
|
|
165
|
+
run.config.testset_id,
|
|
166
|
+
run.config.adapter_path,
|
|
167
|
+
run.config.correctness_judge_model,
|
|
168
|
+
run.config.groundedness_judge_model,
|
|
169
|
+
int(run.config.run_ablation),
|
|
170
|
+
run.config.started_at.isoformat(),
|
|
171
|
+
run.finished_at.isoformat() if run.finished_at else None,
|
|
172
|
+
run.summary.model_dump_json() if run.summary is not None else None,
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
for position, scored_case in enumerate(run.scored_cases):
|
|
177
|
+
conn.execute(
|
|
178
|
+
"INSERT INTO scored_cases (run_id, test_id, position, data_json) VALUES (?, ?, ?, ?)",
|
|
179
|
+
(run_id, scored_case.test_id, position, scored_case.model_dump_json()),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
conn.commit()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def load_run(conn: sqlite3.Connection, run_id: str) -> Run | None:
|
|
186
|
+
"""Load a Run by id, with its ScoredCases in their original order. None if not found."""
|
|
187
|
+
row = conn.execute(
|
|
188
|
+
"""
|
|
189
|
+
SELECT run_id, testset_id, adapter_path, correctness_judge_model,
|
|
190
|
+
groundedness_judge_model, run_ablation, started_at, finished_at, summary_json
|
|
191
|
+
FROM runs WHERE run_id = ?
|
|
192
|
+
""",
|
|
193
|
+
(run_id,),
|
|
194
|
+
).fetchone()
|
|
195
|
+
if row is None:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
config = RunConfig(
|
|
199
|
+
run_id=row[0],
|
|
200
|
+
testset_id=row[1],
|
|
201
|
+
adapter_path=row[2],
|
|
202
|
+
correctness_judge_model=row[3],
|
|
203
|
+
groundedness_judge_model=row[4],
|
|
204
|
+
run_ablation=bool(row[5]),
|
|
205
|
+
started_at=datetime.fromisoformat(row[6]),
|
|
206
|
+
)
|
|
207
|
+
finished_at = datetime.fromisoformat(row[7]) if row[7] else None
|
|
208
|
+
summary = RunSummary.model_validate_json(row[8]) if row[8] is not None else None
|
|
209
|
+
|
|
210
|
+
case_rows = conn.execute(
|
|
211
|
+
"SELECT data_json FROM scored_cases WHERE run_id = ? ORDER BY position ASC",
|
|
212
|
+
(run_id,),
|
|
213
|
+
).fetchall()
|
|
214
|
+
scored_cases = [ScoredCase.model_validate_json(r[0]) for r in case_rows]
|
|
215
|
+
|
|
216
|
+
return Run(config=config, finished_at=finished_at, scored_cases=scored_cases, summary=summary)
|