t2s-metrics 1.0.0__tar.gz
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.
- t2s_metrics-1.0.0/PKG-INFO +335 -0
- t2s_metrics-1.0.0/README.md +301 -0
- t2s_metrics-1.0.0/setup.cfg +4 -0
- t2s_metrics-1.0.0/setup.py +40 -0
- t2s_metrics-1.0.0/t2s_metrics.egg-info/PKG-INFO +335 -0
- t2s_metrics-1.0.0/t2s_metrics.egg-info/SOURCES.txt +68 -0
- t2s_metrics-1.0.0/t2s_metrics.egg-info/dependency_links.txt +1 -0
- t2s_metrics-1.0.0/t2s_metrics.egg-info/requires.txt +14 -0
- t2s_metrics-1.0.0/t2s_metrics.egg-info/top_level.txt +1 -0
- t2s_metrics-1.0.0/t2smetrics/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/aggregation/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/aggregation/aggregator.py +16 -0
- t2s_metrics-1.0.0/t2smetrics/cli.py +35 -0
- t2s_metrics-1.0.0/t2smetrics/core/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/core/context.py +10 -0
- t2s_metrics-1.0.0/t2smetrics/core/engine.py +45 -0
- t2s_metrics-1.0.0/t2smetrics/core/eval.py +41 -0
- t2s_metrics-1.0.0/t2smetrics/core/experiment.py +32 -0
- t2s_metrics-1.0.0/t2smetrics/core/result.py +11 -0
- t2s_metrics-1.0.0/t2smetrics/dashboard_plotly.py +545 -0
- t2s_metrics-1.0.0/t2smetrics/execution/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/execution/base.py +3 -0
- t2s_metrics-1.0.0/t2smetrics/execution/rdflib_backend.py +24 -0
- t2s_metrics-1.0.0/t2smetrics/execution/result_utils.py +61 -0
- t2s_metrics-1.0.0/t2smetrics/execution/sparql_endpoint_backend.py +41 -0
- t2s_metrics-1.0.0/t2smetrics/llm/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/llm/base.py +3 -0
- t2s_metrics-1.0.0/t2smetrics/llm/dummy_backend.py +10 -0
- t2s_metrics-1.0.0/t2smetrics/llm/ollama_backend.py +51 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/base.py +69 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/f1.py +22 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/f1_qald.py +22 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/f1_spinach.py +168 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/hit_at_k.py +22 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/mrr.py +19 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/ndcg.py +36 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/p_at_k.py +25 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/precision.py +16 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/precision_qald.py +19 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/recall.py +16 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/answer_set/recall_qald.py +19 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/base.py +22 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/codebleu.py +44 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/dataflow.py +18 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/sparql_keywords.py +44 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/syntax.py +9 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/codebleu/weighted_bleu.py +18 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/distance.py +51 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/exact.py +12 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/llm_judge.py +14 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/query_execution.py +16 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/text_metrics.py +93 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/token.py +55 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/uri/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/metrics/uri/uri_hallucination.py +33 -0
- t2s_metrics-1.0.0/t2smetrics/representation/__init__.py +0 -0
- t2s_metrics-1.0.0/t2smetrics/representation/preprocessing.py +104 -0
- t2s_metrics-1.0.0/t2smetrics/representation/sparql_query.py +19 -0
- t2s_metrics-1.0.0/t2smetrics/representation/tokenizer.py +32 -0
- t2s_metrics-1.0.0/t2smetrics/representation/uri_utils.py +46 -0
- t2s_metrics-1.0.0/t2smetrics/run_dblp_quad.py +139 -0
- t2s_metrics-1.0.0/t2smetrics/run_example.py +88 -0
- t2s_metrics-1.0.0/t2smetrics/run_example_2.py +14 -0
- t2s_metrics-1.0.0/t2smetrics/run_instruct_sparql.py +141 -0
- t2s_metrics-1.0.0/t2smetrics/run_pathological_dataset.py +111 -0
- t2s_metrics-1.0.0/t2smetrics/run_text2sparql.py +141 -0
- t2s_metrics-1.0.0/test/test_metrics.py +41 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: t2s-metrics
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Home-page: https://github.com/Wimmics/t2s-metrics
|
|
5
|
+
Author: Wimmics
|
|
6
|
+
Author-email: yousouf.taghzouti@inria.fr
|
|
7
|
+
License: AGPL-3.0
|
|
8
|
+
Keywords: Question Answering System,Large Language Model,Metric,Text-to-SPARQL
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: setuptools==80.9.0
|
|
12
|
+
Requires-Dist: wheel==0.45.1
|
|
13
|
+
Requires-Dist: twine==6.2.0
|
|
14
|
+
Requires-Dist: nltk==3.9.2
|
|
15
|
+
Requires-Dist: rouge_score==0.1.2
|
|
16
|
+
Requires-Dist: scikit-learn==1.8.0
|
|
17
|
+
Requires-Dist: Levenshtein==0.27.3
|
|
18
|
+
Requires-Dist: rdflib==7.5.0
|
|
19
|
+
Requires-Dist: langchain_ollama==1.0.1
|
|
20
|
+
Requires-Dist: SPARQLWrapper==2.0.0
|
|
21
|
+
Requires-Dist: pytest==9.0.2
|
|
22
|
+
Requires-Dist: dash==4.0.0
|
|
23
|
+
Requires-Dist: dash_bootstrap_components==2.0.4
|
|
24
|
+
Requires-Dist: pandas==3.0.1
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
|
|
35
|
+
# T2S-Metrics
|
|
36
|
+
|
|
37
|
+
A small evaluation toolkit for text-to-SPARQL systems. It runs a configurable set
|
|
38
|
+
of metrics over JSONL datasets and can execute queries against local RDF files or
|
|
39
|
+
SPARQL endpoints.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Metrics for query exact match, token overlap, answer-set quality, BLEU/ROUGE,
|
|
44
|
+
CodeBLEU, and more.
|
|
45
|
+
- Execution backends for local RDF (RDFLib) and remote SPARQL endpoints.
|
|
46
|
+
- Pluggable LLM-based judging via an Ollama backend.
|
|
47
|
+
- Python API for quick experiments.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
The package is available on PyPI and can be installed directly with pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install t2s-metrics
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For development (editable install), you can use:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv venv
|
|
61
|
+
source .venv/bin/activate
|
|
62
|
+
uv pip install -r requirements.txt
|
|
63
|
+
uv pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Expected JSONL format
|
|
69
|
+
|
|
70
|
+
Input evaluation files must be JSON Lines (`.jsonl`) with **one object per line**.
|
|
71
|
+
Each object must include:
|
|
72
|
+
|
|
73
|
+
- `id` (string): unique query/case identifier
|
|
74
|
+
- `golden` (string): reference SPARQL query
|
|
75
|
+
- `generated` (string): system-generated SPARQL query
|
|
76
|
+
- `order_matters` (boolean): whether answer order must be preserved
|
|
77
|
+
|
|
78
|
+
This is exactly what `JsonlEval` expects in `t2smetrics/core/eval.py`.
|
|
79
|
+
|
|
80
|
+
Example (from `datasets/ck25/eval/AIFB.jsonl`):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{"id": "ck25:1-en", "golden": "PREFIX pv: <http://ld.company.org/prod-vocab/>\nSELECT DISTINCT ?result\nWHERE\n{\n <http://ld.company.org/prod-instances/empl-Karen.Brant%40company.org> pv:memberOf ?result .\n ?result a pv:Department .\n}\n", "generated": "SELECT ?department WHERE { ?person :name \"Ms. Brant\"; :worksIn ?department. }", "order_matters": false}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Execution backends
|
|
87
|
+
|
|
88
|
+
The library supports two execution backend families:
|
|
89
|
+
|
|
90
|
+
1. **Local RDF file execution** with `RDFLibBackend`
|
|
91
|
+
2. **Remote SPARQL endpoint execution** with `SparqlEndpointBackend`
|
|
92
|
+
|
|
93
|
+
`SparqlEndpointBackend` is generic SPARQL 1.1 and works with endpoints such as
|
|
94
|
+
**QLever** and **Corese** (and also GraphDB, Fuseki, Virtuoso, Blazegraph, etc.).
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from t2smetrics.execution.rdflib_backend import RDFLibBackend
|
|
98
|
+
from t2smetrics.execution.sparql_endpoint_backend import SparqlEndpointBackend
|
|
99
|
+
|
|
100
|
+
# Option 1: local KG file
|
|
101
|
+
local_backend = RDFLibBackend("./datasets/example/kg/example.ttl")
|
|
102
|
+
|
|
103
|
+
# Option 2: remote endpoint (e.g., QLever/Corese)
|
|
104
|
+
endpoint_backend = SparqlEndpointBackend("http://localhost:8886/")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### LLM backend (local Ollama + extensible)
|
|
108
|
+
|
|
109
|
+
For LLM-based metrics (for example `LLMJudge`), the library currently provides
|
|
110
|
+
`OllamaBackend` for **local** inference.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from t2smetrics.llm.ollama_backend import OllamaBackend
|
|
114
|
+
|
|
115
|
+
llm_backend = OllamaBackend(model="gemma3:4b")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The LLM layer is extensible via `LLMBackend` (`t2smetrics/llm/base.py`).
|
|
119
|
+
To plug another provider, implement `judge(prompt: str, timeout: int = 30) -> dict`
|
|
120
|
+
and return a dictionary with a numeric `score` (recommended in `[0, 1]`).
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from t2smetrics.llm.base import LLMBackend
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class MyLLMBackend(LLMBackend):
|
|
127
|
+
def judge(self, prompt: str, timeout: int = 30) -> dict:
|
|
128
|
+
# Call your provider/client here
|
|
129
|
+
return {"score": 0.85, "raw": "optional provider response"}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then pass your backend to `Experiment(..., llm_backend=...)`.
|
|
133
|
+
|
|
134
|
+
### Python (minimal example)
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from t2smetrics.core.experiment import Experiment
|
|
138
|
+
from t2smetrics.core.eval import JsonlEval
|
|
139
|
+
from t2smetrics.metrics.text_metrics import Bleu
|
|
140
|
+
from t2smetrics.metrics.token import TokenF1
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
jsonl_eval = JsonlEval("./datasets/example/eval/example.jsonl")
|
|
144
|
+
metrics = [Bleu(), TokenF1()]
|
|
145
|
+
experiment = Experiment(jsonl_eval, metrics)
|
|
146
|
+
_, summary = experiment.run()
|
|
147
|
+
|
|
148
|
+
print("\n=== SUMMARY ===")
|
|
149
|
+
for k, v in summary.items():
|
|
150
|
+
print(f"{k}: {v:.4f}")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Python (full example with execution backends)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from t2smetrics.core.experiment import Experiment
|
|
157
|
+
from t2smetrics.core.eval import JsonlEval
|
|
158
|
+
from t2smetrics.execution.rdflib_backend import RDFLibBackend
|
|
159
|
+
|
|
160
|
+
from t2smetrics.llm.ollama_backend import OllamaBackend
|
|
161
|
+
from t2smetrics.metrics.answer_set.f1 import AnswerSetF1
|
|
162
|
+
from t2smetrics.metrics.answer_set.precision import AnswerSetPrecision
|
|
163
|
+
from t2smetrics.metrics.answer_set.precision_qald import PrecisionQALD
|
|
164
|
+
from t2smetrics.metrics.answer_set.recall import AnswerSetRecall
|
|
165
|
+
from t2smetrics.metrics.answer_set.recall_qald import RecallQALD
|
|
166
|
+
from t2smetrics.metrics.exact import QueryExactMatch
|
|
167
|
+
from t2smetrics.metrics.codebleu.codebleu import CodeBLEU
|
|
168
|
+
from t2smetrics.metrics.answer_set.f1_qald import F1QALD
|
|
169
|
+
from t2smetrics.metrics.answer_set.f1_spinach import F1Spinach
|
|
170
|
+
from t2smetrics.metrics.answer_set.mrr import MRR
|
|
171
|
+
from t2smetrics.metrics.answer_set.hit_at_k import HitAtK
|
|
172
|
+
from t2smetrics.metrics.answer_set.ndcg import NDCG
|
|
173
|
+
from t2smetrics.metrics.answer_set.p_at_k import PrecisionAtK
|
|
174
|
+
from t2smetrics.metrics.distance import (
|
|
175
|
+
LevenshteinDistance,
|
|
176
|
+
JaccardSimilarity,
|
|
177
|
+
CosineSimilarity,
|
|
178
|
+
EuclideanDistance,
|
|
179
|
+
)
|
|
180
|
+
from t2smetrics.metrics.llm_judge import LLMJudge
|
|
181
|
+
from t2smetrics.metrics.text_metrics import Bleu, RougeN, Meteor, SPBleu
|
|
182
|
+
from t2smetrics.metrics.uri.uri_hallucination import URIHallucination
|
|
183
|
+
from t2smetrics.metrics.query_execution import QueryExecution
|
|
184
|
+
from t2smetrics.metrics.token import SPF1, TokenRecall, TokenPrecision, TokenF1
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
jsonl_eval = JsonlEval("./datasets/example/eval/example.jsonl")
|
|
188
|
+
|
|
189
|
+
execution_backend = RDFLibBackend("./datasets/example/kg/example.ttl")
|
|
190
|
+
|
|
191
|
+
llm_backend = OllamaBackend()
|
|
192
|
+
|
|
193
|
+
metrics = [
|
|
194
|
+
AnswerSetPrecision(),
|
|
195
|
+
AnswerSetRecall(),
|
|
196
|
+
AnswerSetF1(),
|
|
197
|
+
Bleu(),
|
|
198
|
+
SPBleu(),
|
|
199
|
+
CodeBLEU(),
|
|
200
|
+
CosineSimilarity(),
|
|
201
|
+
EuclideanDistance(),
|
|
202
|
+
F1QALD(),
|
|
203
|
+
PrecisionQALD(),
|
|
204
|
+
RecallQALD(),
|
|
205
|
+
F1Spinach(),
|
|
206
|
+
HitAtK(k=5),
|
|
207
|
+
JaccardSimilarity(),
|
|
208
|
+
LLMJudge(),
|
|
209
|
+
LevenshteinDistance(),
|
|
210
|
+
MRR(),
|
|
211
|
+
Meteor(),
|
|
212
|
+
NDCG(),
|
|
213
|
+
PrecisionAtK(k=1),
|
|
214
|
+
QueryExecution(),
|
|
215
|
+
QueryExactMatch(),
|
|
216
|
+
RougeN(1),
|
|
217
|
+
RougeN(2),
|
|
218
|
+
RougeN(3),
|
|
219
|
+
RougeN(4),
|
|
220
|
+
TokenF1(),
|
|
221
|
+
SPF1(),
|
|
222
|
+
TokenPrecision(),
|
|
223
|
+
TokenRecall(),
|
|
224
|
+
URIHallucination(),
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
experiment = Experiment(
|
|
228
|
+
jsonl_eval=jsonl_eval,
|
|
229
|
+
metrics=metrics,
|
|
230
|
+
execution_backend=execution_backend,
|
|
231
|
+
llm_backend=llm_backend,
|
|
232
|
+
verbose=True,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
results, summary = experiment.run()
|
|
236
|
+
|
|
237
|
+
print("=== PER QUERY RESULTS ===")
|
|
238
|
+
for r in results:
|
|
239
|
+
print(r)
|
|
240
|
+
|
|
241
|
+
print("\n=== SUMMARY ===")
|
|
242
|
+
for k, v in summary.items():
|
|
243
|
+
print(f"{k}: {v:.4f}")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Full workflow example (dataset + endpoint + export)
|
|
247
|
+
|
|
248
|
+
For a complete run over multiple systems and export of aggregated metrics to JSON,
|
|
249
|
+
see `t2smetrics/run_text2sparql.py`.
|
|
250
|
+
|
|
251
|
+
Typical workflow:
|
|
252
|
+
|
|
253
|
+
1. Choose a dataset folder (for example `datasets/ck25`).
|
|
254
|
+
2. Put input files under `datasets/<dataset>/eval/*.jsonl`.
|
|
255
|
+
3. Start your SPARQL endpoint (for example QLever/Corese).
|
|
256
|
+
4. Set endpoint URL in the script (example: `http://localhost:8886/`).
|
|
257
|
+
5. Run:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
python -m t2smetrics.run_text2sparql
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The script writes timestamped summary files under:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
datasets/<dataset>/results/<dataset>-YYYYMMDD-HHMMSS.json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
These result files are then directly consumable by the dashboard.
|
|
270
|
+
|
|
271
|
+
### Dashboard
|
|
272
|
+
|
|
273
|
+
The dashboard reads JSON result files (generated in `datasets/*/results/*.json`)
|
|
274
|
+
and serves an interactive UI (Radar, Bar, Correlation Heatmap, Parallel Coordinates,
|
|
275
|
+
Scatter Matrix).
|
|
276
|
+
|
|
277
|
+
Launch with auto-discovery:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
python -m t2smetrics.cli dashboard
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Launch with explicit files:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
python -m t2smetrics.cli dashboard \
|
|
287
|
+
datasets/ck25/results/ck25-20260306-133227.json \
|
|
288
|
+
datasets/db25/results/db25-20260306-132100.json
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Then open:
|
|
292
|
+
|
|
293
|
+
```text
|
|
294
|
+
http://127.0.0.1:8050
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
### Build
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
python setup.py sdist bdist_wheel
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Tests
|
|
307
|
+
|
|
308
|
+
There are no automated tests yet. If you add tests, run them with:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
python -m pytest
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
# License
|
|
315
|
+
|
|
316
|
+
## t2s-metrics
|
|
317
|
+
|
|
318
|
+
t2s-metrics is provided under the terms of the [GNU Affero General Public License 3.0](./LICENSES/AGPL-3.0.txt) (AGPL-3.0).
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
## Redistribution of third-party software and data
|
|
322
|
+
|
|
323
|
+
This repository provides several third-party contributions redistributed with their original licenses.
|
|
324
|
+
|
|
325
|
+
### CK25 Dataset
|
|
326
|
+
|
|
327
|
+
t2s-metrics reuses the [CK25 Corporate Knowledge Reference Dataset for Benchmarking Text-2-SPARQL QA Approaches](https://github.com/eccenca/ck25-dataset/) that we modified to account for file format requirements (jsonl format).
|
|
328
|
+
|
|
329
|
+
The modified version is redistributed in directory [dataset/ck25](dataset/ck25) under the terms of the [Creative Commons Attribution 4.0 International license](LICENSES/CC-BY-4.0.txt) (CC-BY-4.0).
|
|
330
|
+
|
|
331
|
+
### QCan library
|
|
332
|
+
|
|
333
|
+
t2s-metrics reuses the [QCan software for canonicalising SPARQL queries](https://github.com/RittoShadow/QCan).
|
|
334
|
+
|
|
335
|
+
QCan is written in Java. In this repository, we distribute the compiled jar of QCan v1.1, [third_party_lib/qcan-1.1-jar-with-dependencies.jar](third_party_lib/qcan-1.1-jar-with-dependencies.jar), under the terms of the [Apache 2.0 license](LICENSES/Apache-2.0.txt).
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# T2S-Metrics
|
|
2
|
+
|
|
3
|
+
A small evaluation toolkit for text-to-SPARQL systems. It runs a configurable set
|
|
4
|
+
of metrics over JSONL datasets and can execute queries against local RDF files or
|
|
5
|
+
SPARQL endpoints.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Metrics for query exact match, token overlap, answer-set quality, BLEU/ROUGE,
|
|
10
|
+
CodeBLEU, and more.
|
|
11
|
+
- Execution backends for local RDF (RDFLib) and remote SPARQL endpoints.
|
|
12
|
+
- Pluggable LLM-based judging via an Ollama backend.
|
|
13
|
+
- Python API for quick experiments.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
The package is available on PyPI and can be installed directly with pip:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install t2s-metrics
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For development (editable install), you can use:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv venv
|
|
27
|
+
source .venv/bin/activate
|
|
28
|
+
uv pip install -r requirements.txt
|
|
29
|
+
uv pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Expected JSONL format
|
|
35
|
+
|
|
36
|
+
Input evaluation files must be JSON Lines (`.jsonl`) with **one object per line**.
|
|
37
|
+
Each object must include:
|
|
38
|
+
|
|
39
|
+
- `id` (string): unique query/case identifier
|
|
40
|
+
- `golden` (string): reference SPARQL query
|
|
41
|
+
- `generated` (string): system-generated SPARQL query
|
|
42
|
+
- `order_matters` (boolean): whether answer order must be preserved
|
|
43
|
+
|
|
44
|
+
This is exactly what `JsonlEval` expects in `t2smetrics/core/eval.py`.
|
|
45
|
+
|
|
46
|
+
Example (from `datasets/ck25/eval/AIFB.jsonl`):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{"id": "ck25:1-en", "golden": "PREFIX pv: <http://ld.company.org/prod-vocab/>\nSELECT DISTINCT ?result\nWHERE\n{\n <http://ld.company.org/prod-instances/empl-Karen.Brant%40company.org> pv:memberOf ?result .\n ?result a pv:Department .\n}\n", "generated": "SELECT ?department WHERE { ?person :name \"Ms. Brant\"; :worksIn ?department. }", "order_matters": false}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Execution backends
|
|
53
|
+
|
|
54
|
+
The library supports two execution backend families:
|
|
55
|
+
|
|
56
|
+
1. **Local RDF file execution** with `RDFLibBackend`
|
|
57
|
+
2. **Remote SPARQL endpoint execution** with `SparqlEndpointBackend`
|
|
58
|
+
|
|
59
|
+
`SparqlEndpointBackend` is generic SPARQL 1.1 and works with endpoints such as
|
|
60
|
+
**QLever** and **Corese** (and also GraphDB, Fuseki, Virtuoso, Blazegraph, etc.).
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from t2smetrics.execution.rdflib_backend import RDFLibBackend
|
|
64
|
+
from t2smetrics.execution.sparql_endpoint_backend import SparqlEndpointBackend
|
|
65
|
+
|
|
66
|
+
# Option 1: local KG file
|
|
67
|
+
local_backend = RDFLibBackend("./datasets/example/kg/example.ttl")
|
|
68
|
+
|
|
69
|
+
# Option 2: remote endpoint (e.g., QLever/Corese)
|
|
70
|
+
endpoint_backend = SparqlEndpointBackend("http://localhost:8886/")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### LLM backend (local Ollama + extensible)
|
|
74
|
+
|
|
75
|
+
For LLM-based metrics (for example `LLMJudge`), the library currently provides
|
|
76
|
+
`OllamaBackend` for **local** inference.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from t2smetrics.llm.ollama_backend import OllamaBackend
|
|
80
|
+
|
|
81
|
+
llm_backend = OllamaBackend(model="gemma3:4b")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The LLM layer is extensible via `LLMBackend` (`t2smetrics/llm/base.py`).
|
|
85
|
+
To plug another provider, implement `judge(prompt: str, timeout: int = 30) -> dict`
|
|
86
|
+
and return a dictionary with a numeric `score` (recommended in `[0, 1]`).
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from t2smetrics.llm.base import LLMBackend
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MyLLMBackend(LLMBackend):
|
|
93
|
+
def judge(self, prompt: str, timeout: int = 30) -> dict:
|
|
94
|
+
# Call your provider/client here
|
|
95
|
+
return {"score": 0.85, "raw": "optional provider response"}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then pass your backend to `Experiment(..., llm_backend=...)`.
|
|
99
|
+
|
|
100
|
+
### Python (minimal example)
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from t2smetrics.core.experiment import Experiment
|
|
104
|
+
from t2smetrics.core.eval import JsonlEval
|
|
105
|
+
from t2smetrics.metrics.text_metrics import Bleu
|
|
106
|
+
from t2smetrics.metrics.token import TokenF1
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
jsonl_eval = JsonlEval("./datasets/example/eval/example.jsonl")
|
|
110
|
+
metrics = [Bleu(), TokenF1()]
|
|
111
|
+
experiment = Experiment(jsonl_eval, metrics)
|
|
112
|
+
_, summary = experiment.run()
|
|
113
|
+
|
|
114
|
+
print("\n=== SUMMARY ===")
|
|
115
|
+
for k, v in summary.items():
|
|
116
|
+
print(f"{k}: {v:.4f}")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Python (full example with execution backends)
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from t2smetrics.core.experiment import Experiment
|
|
123
|
+
from t2smetrics.core.eval import JsonlEval
|
|
124
|
+
from t2smetrics.execution.rdflib_backend import RDFLibBackend
|
|
125
|
+
|
|
126
|
+
from t2smetrics.llm.ollama_backend import OllamaBackend
|
|
127
|
+
from t2smetrics.metrics.answer_set.f1 import AnswerSetF1
|
|
128
|
+
from t2smetrics.metrics.answer_set.precision import AnswerSetPrecision
|
|
129
|
+
from t2smetrics.metrics.answer_set.precision_qald import PrecisionQALD
|
|
130
|
+
from t2smetrics.metrics.answer_set.recall import AnswerSetRecall
|
|
131
|
+
from t2smetrics.metrics.answer_set.recall_qald import RecallQALD
|
|
132
|
+
from t2smetrics.metrics.exact import QueryExactMatch
|
|
133
|
+
from t2smetrics.metrics.codebleu.codebleu import CodeBLEU
|
|
134
|
+
from t2smetrics.metrics.answer_set.f1_qald import F1QALD
|
|
135
|
+
from t2smetrics.metrics.answer_set.f1_spinach import F1Spinach
|
|
136
|
+
from t2smetrics.metrics.answer_set.mrr import MRR
|
|
137
|
+
from t2smetrics.metrics.answer_set.hit_at_k import HitAtK
|
|
138
|
+
from t2smetrics.metrics.answer_set.ndcg import NDCG
|
|
139
|
+
from t2smetrics.metrics.answer_set.p_at_k import PrecisionAtK
|
|
140
|
+
from t2smetrics.metrics.distance import (
|
|
141
|
+
LevenshteinDistance,
|
|
142
|
+
JaccardSimilarity,
|
|
143
|
+
CosineSimilarity,
|
|
144
|
+
EuclideanDistance,
|
|
145
|
+
)
|
|
146
|
+
from t2smetrics.metrics.llm_judge import LLMJudge
|
|
147
|
+
from t2smetrics.metrics.text_metrics import Bleu, RougeN, Meteor, SPBleu
|
|
148
|
+
from t2smetrics.metrics.uri.uri_hallucination import URIHallucination
|
|
149
|
+
from t2smetrics.metrics.query_execution import QueryExecution
|
|
150
|
+
from t2smetrics.metrics.token import SPF1, TokenRecall, TokenPrecision, TokenF1
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
jsonl_eval = JsonlEval("./datasets/example/eval/example.jsonl")
|
|
154
|
+
|
|
155
|
+
execution_backend = RDFLibBackend("./datasets/example/kg/example.ttl")
|
|
156
|
+
|
|
157
|
+
llm_backend = OllamaBackend()
|
|
158
|
+
|
|
159
|
+
metrics = [
|
|
160
|
+
AnswerSetPrecision(),
|
|
161
|
+
AnswerSetRecall(),
|
|
162
|
+
AnswerSetF1(),
|
|
163
|
+
Bleu(),
|
|
164
|
+
SPBleu(),
|
|
165
|
+
CodeBLEU(),
|
|
166
|
+
CosineSimilarity(),
|
|
167
|
+
EuclideanDistance(),
|
|
168
|
+
F1QALD(),
|
|
169
|
+
PrecisionQALD(),
|
|
170
|
+
RecallQALD(),
|
|
171
|
+
F1Spinach(),
|
|
172
|
+
HitAtK(k=5),
|
|
173
|
+
JaccardSimilarity(),
|
|
174
|
+
LLMJudge(),
|
|
175
|
+
LevenshteinDistance(),
|
|
176
|
+
MRR(),
|
|
177
|
+
Meteor(),
|
|
178
|
+
NDCG(),
|
|
179
|
+
PrecisionAtK(k=1),
|
|
180
|
+
QueryExecution(),
|
|
181
|
+
QueryExactMatch(),
|
|
182
|
+
RougeN(1),
|
|
183
|
+
RougeN(2),
|
|
184
|
+
RougeN(3),
|
|
185
|
+
RougeN(4),
|
|
186
|
+
TokenF1(),
|
|
187
|
+
SPF1(),
|
|
188
|
+
TokenPrecision(),
|
|
189
|
+
TokenRecall(),
|
|
190
|
+
URIHallucination(),
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
experiment = Experiment(
|
|
194
|
+
jsonl_eval=jsonl_eval,
|
|
195
|
+
metrics=metrics,
|
|
196
|
+
execution_backend=execution_backend,
|
|
197
|
+
llm_backend=llm_backend,
|
|
198
|
+
verbose=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
results, summary = experiment.run()
|
|
202
|
+
|
|
203
|
+
print("=== PER QUERY RESULTS ===")
|
|
204
|
+
for r in results:
|
|
205
|
+
print(r)
|
|
206
|
+
|
|
207
|
+
print("\n=== SUMMARY ===")
|
|
208
|
+
for k, v in summary.items():
|
|
209
|
+
print(f"{k}: {v:.4f}")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Full workflow example (dataset + endpoint + export)
|
|
213
|
+
|
|
214
|
+
For a complete run over multiple systems and export of aggregated metrics to JSON,
|
|
215
|
+
see `t2smetrics/run_text2sparql.py`.
|
|
216
|
+
|
|
217
|
+
Typical workflow:
|
|
218
|
+
|
|
219
|
+
1. Choose a dataset folder (for example `datasets/ck25`).
|
|
220
|
+
2. Put input files under `datasets/<dataset>/eval/*.jsonl`.
|
|
221
|
+
3. Start your SPARQL endpoint (for example QLever/Corese).
|
|
222
|
+
4. Set endpoint URL in the script (example: `http://localhost:8886/`).
|
|
223
|
+
5. Run:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
python -m t2smetrics.run_text2sparql
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The script writes timestamped summary files under:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
datasets/<dataset>/results/<dataset>-YYYYMMDD-HHMMSS.json
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
These result files are then directly consumable by the dashboard.
|
|
236
|
+
|
|
237
|
+
### Dashboard
|
|
238
|
+
|
|
239
|
+
The dashboard reads JSON result files (generated in `datasets/*/results/*.json`)
|
|
240
|
+
and serves an interactive UI (Radar, Bar, Correlation Heatmap, Parallel Coordinates,
|
|
241
|
+
Scatter Matrix).
|
|
242
|
+
|
|
243
|
+
Launch with auto-discovery:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
python -m t2smetrics.cli dashboard
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Launch with explicit files:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
python -m t2smetrics.cli dashboard \
|
|
253
|
+
datasets/ck25/results/ck25-20260306-133227.json \
|
|
254
|
+
datasets/db25/results/db25-20260306-132100.json
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Then open:
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
http://127.0.0.1:8050
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
## Development
|
|
265
|
+
|
|
266
|
+
### Build
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
python setup.py sdist bdist_wheel
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Tests
|
|
273
|
+
|
|
274
|
+
There are no automated tests yet. If you add tests, run them with:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
python -m pytest
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
# License
|
|
281
|
+
|
|
282
|
+
## t2s-metrics
|
|
283
|
+
|
|
284
|
+
t2s-metrics is provided under the terms of the [GNU Affero General Public License 3.0](./LICENSES/AGPL-3.0.txt) (AGPL-3.0).
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
## Redistribution of third-party software and data
|
|
288
|
+
|
|
289
|
+
This repository provides several third-party contributions redistributed with their original licenses.
|
|
290
|
+
|
|
291
|
+
### CK25 Dataset
|
|
292
|
+
|
|
293
|
+
t2s-metrics reuses the [CK25 Corporate Knowledge Reference Dataset for Benchmarking Text-2-SPARQL QA Approaches](https://github.com/eccenca/ck25-dataset/) that we modified to account for file format requirements (jsonl format).
|
|
294
|
+
|
|
295
|
+
The modified version is redistributed in directory [dataset/ck25](dataset/ck25) under the terms of the [Creative Commons Attribution 4.0 International license](LICENSES/CC-BY-4.0.txt) (CC-BY-4.0).
|
|
296
|
+
|
|
297
|
+
### QCan library
|
|
298
|
+
|
|
299
|
+
t2s-metrics reuses the [QCan software for canonicalising SPARQL queries](https://github.com/RittoShadow/QCan).
|
|
300
|
+
|
|
301
|
+
QCan is written in Java. In this repository, we distribute the compiled jar of QCan v1.1, [third_party_lib/qcan-1.1-jar-with-dependencies.jar](third_party_lib/qcan-1.1-jar-with-dependencies.jar), under the terms of the [Apache 2.0 license](LICENSES/Apache-2.0.txt).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="t2s-metrics",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
packages=find_packages(),
|
|
10
|
+
install_requires=[
|
|
11
|
+
"setuptools==80.9.0",
|
|
12
|
+
"wheel==0.45.1",
|
|
13
|
+
"twine==6.2.0",
|
|
14
|
+
"nltk==3.9.2",
|
|
15
|
+
"rouge_score==0.1.2",
|
|
16
|
+
"scikit-learn==1.8.0",
|
|
17
|
+
"Levenshtein==0.27.3",
|
|
18
|
+
"rdflib==7.5.0",
|
|
19
|
+
"langchain_ollama==1.0.1",
|
|
20
|
+
"SPARQLWrapper==2.0.0",
|
|
21
|
+
"pytest==9.0.2",
|
|
22
|
+
"dash==4.0.0",
|
|
23
|
+
"dash_bootstrap_components==2.0.4",
|
|
24
|
+
"pandas==3.0.1",
|
|
25
|
+
],
|
|
26
|
+
long_description=long_description,
|
|
27
|
+
long_description_content_type="text/markdown",
|
|
28
|
+
author="Wimmics",
|
|
29
|
+
author_email="yousouf.taghzouti@inria.fr",
|
|
30
|
+
url="https://github.com/Wimmics/t2s-metrics",
|
|
31
|
+
licence="AGPL-3.0",
|
|
32
|
+
license_files=["LICENSE"],
|
|
33
|
+
keywords=[
|
|
34
|
+
"Question Answering System",
|
|
35
|
+
"Large Language Model",
|
|
36
|
+
"Metric",
|
|
37
|
+
"Text-to-SPARQL"
|
|
38
|
+
],
|
|
39
|
+
python_requires=">=3.12",
|
|
40
|
+
)
|