graphrag-eval 6.4.0__tar.gz → 6.5.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.
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/PKG-INFO +2 -2
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/answer_correctness.py +39 -43
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/cli/answer_correctness.py +11 -3
- graphrag_eval-6.5.0/graphrag_eval/prompts/template.md +31 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/pyproject.toml +6 -6
- graphrag_eval-6.4.0/graphrag_eval/prompts/template.md +0 -26
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/LICENSE +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/README.md +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/__init__.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/aggregation.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/answer_relevance.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/cli/__init__.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/custom_evaluation.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/evaluation.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/evaluator.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/llm_factory.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/__init__.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/evaluation.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/iri_discovery.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_answer.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_context_ids.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_context_texts.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/sparql.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/timeseries.py +0 -0
- {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: graphrag-eval
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.5.0
|
|
4
4
|
Summary: For assessing question answering systems' final answers and intermediate steps, against a given set of questions, reference answers and steps.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Philip Ganchev
|
|
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Provides-Extra: llm
|
|
13
|
-
Requires-Dist: pydantic (==2.
|
|
13
|
+
Requires-Dist: pydantic (==2.14.1)
|
|
14
14
|
Requires-Dist: python-dateutil (==2.9.0.post0)
|
|
15
15
|
Requires-Dist: ragas (==0.4.3) ; extra == "llm"
|
|
16
16
|
Project-URL: Repository, https://github.com/Ontotext-AD/graphrag-eval
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Any, Self, TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
7
7
|
|
|
8
8
|
from graphrag_eval.util import compute_f1
|
|
9
9
|
from .evaluator import Evaluator
|
|
@@ -25,6 +25,27 @@ class AnswerCorrectnessConfig(BaseModel):
|
|
|
25
25
|
prompt: str = Field(default_factory=load_default_prompt)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class AnswerCorrectnessResult(BaseModel):
|
|
29
|
+
model_config = ConfigDict(extra="forbid")
|
|
30
|
+
|
|
31
|
+
reference_claims_count: int = Field(ge=1)
|
|
32
|
+
actual_claims_count: int = Field(ge=1)
|
|
33
|
+
matching_claims_count: int = Field(ge=0)
|
|
34
|
+
reason: str = Field(min_length=1)
|
|
35
|
+
|
|
36
|
+
@model_validator(mode="after")
|
|
37
|
+
def validate_claim_counts(self) -> "AnswerCorrectnessResult":
|
|
38
|
+
if (
|
|
39
|
+
self.matching_claims_count > self.reference_claims_count or
|
|
40
|
+
self.matching_claims_count > self.actual_claims_count
|
|
41
|
+
):
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"matching_claims_count must be less than or equal to both "
|
|
44
|
+
"reference_claims_count and actual_claims_count"
|
|
45
|
+
)
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
|
|
28
49
|
class InvalidPromptException(Exception):
|
|
29
50
|
def __init__(
|
|
30
51
|
self,
|
|
@@ -73,16 +94,17 @@ class AnswerCorrectnessEvaluator:
|
|
|
73
94
|
f"Original error: {exc}"
|
|
74
95
|
) from exc
|
|
75
96
|
|
|
76
|
-
async def _agenerate(self, prompt):
|
|
97
|
+
async def _agenerate(self, prompt: str) -> AnswerCorrectnessResult:
|
|
77
98
|
"""Wrapper method for easier testing"""
|
|
78
|
-
|
|
99
|
+
response = await self.ragas_llm.agenerate(prompt, AnswerCorrectnessResult)
|
|
100
|
+
return AnswerCorrectnessResult.model_validate(response)
|
|
79
101
|
|
|
80
102
|
async def evaluate_answer(
|
|
81
103
|
self,
|
|
82
104
|
question: str,
|
|
83
105
|
reference_answer: str,
|
|
84
106
|
actual_answer: str
|
|
85
|
-
) ->
|
|
107
|
+
) -> AnswerCorrectnessResult:
|
|
86
108
|
if any(
|
|
87
109
|
not s.strip() for s in [question, reference_answer, actual_answer]
|
|
88
110
|
):
|
|
@@ -95,8 +117,7 @@ class AnswerCorrectnessEvaluator:
|
|
|
95
117
|
reference_answer=reference_answer,
|
|
96
118
|
actual_answer=actual_answer,
|
|
97
119
|
)
|
|
98
|
-
|
|
99
|
-
return self.extract_response_values(response_str)
|
|
120
|
+
return await self._agenerate(prompt)
|
|
100
121
|
|
|
101
122
|
async def evaluate(
|
|
102
123
|
self,
|
|
@@ -107,20 +128,21 @@ class AnswerCorrectnessEvaluator:
|
|
|
107
128
|
return {}
|
|
108
129
|
result = {}
|
|
109
130
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
131
|
+
correctness = await self.evaluate_answer(
|
|
132
|
+
reference["question_text"],
|
|
133
|
+
reference["reference_answer"],
|
|
134
|
+
actual["actual_answer"],
|
|
135
|
+
)
|
|
116
136
|
result.update({
|
|
117
|
-
"answer_reference_claims_count":
|
|
118
|
-
"answer_actual_claims_count":
|
|
119
|
-
"answer_matching_claims_count":
|
|
120
|
-
"answer_correctness_reason": reason,
|
|
137
|
+
"answer_reference_claims_count": correctness.reference_claims_count,
|
|
138
|
+
"answer_actual_claims_count": correctness.actual_claims_count,
|
|
139
|
+
"answer_matching_claims_count": correctness.matching_claims_count,
|
|
140
|
+
"answer_correctness_reason": correctness.reason,
|
|
121
141
|
})
|
|
122
142
|
recall, precision, f1 = self.compute_recall_precision_f1(
|
|
123
|
-
|
|
143
|
+
correctness.reference_claims_count,
|
|
144
|
+
correctness.actual_claims_count,
|
|
145
|
+
correctness.matching_claims_count,
|
|
124
146
|
)
|
|
125
147
|
if recall is not None:
|
|
126
148
|
result["answer_recall"] = recall
|
|
@@ -146,31 +168,5 @@ class AnswerCorrectnessEvaluator:
|
|
|
146
168
|
precision = n_true_pos / n_pred_pos
|
|
147
169
|
return recall, precision, compute_f1(recall, precision)
|
|
148
170
|
|
|
149
|
-
@staticmethod
|
|
150
|
-
def extract_response_values(
|
|
151
|
-
response: str
|
|
152
|
-
) -> tuple[int, int, int, str]:
|
|
153
|
-
vals = response.split("\t")
|
|
154
|
-
n = len(vals)
|
|
155
|
-
if n < 4:
|
|
156
|
-
raise ValueError(f"Expected 4 tab-separated values: {response}")
|
|
157
|
-
vals = vals[:4]
|
|
158
|
-
try:
|
|
159
|
-
n_ref, n_actual, n_matching = map(int, vals[:3])
|
|
160
|
-
except ValueError:
|
|
161
|
-
raise ValueError(f"Claims counts should be ints: {vals}")
|
|
162
|
-
if any([
|
|
163
|
-
n_ref < 1,
|
|
164
|
-
n_actual < 1,
|
|
165
|
-
n_matching < 0,
|
|
166
|
-
n_matching > n_ref,
|
|
167
|
-
n_matching > n_actual
|
|
168
|
-
]):
|
|
169
|
-
raise ValueError(
|
|
170
|
-
"Invalid claims counts combination: "
|
|
171
|
-
f"{n_ref}\t{n_actual}\t{n_matching}"
|
|
172
|
-
)
|
|
173
|
-
return n_ref, n_actual, n_matching, vals[3]
|
|
174
|
-
|
|
175
171
|
|
|
176
172
|
_: Evaluator = AnswerCorrectnessEvaluator
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
5
|
import csv
|
|
6
|
+
import sys
|
|
6
7
|
from argparse import ArgumentParser
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import TYPE_CHECKING
|
|
@@ -55,6 +56,8 @@ async def evaluate_and_write(
|
|
|
55
56
|
output_tsv_file_path: Path,
|
|
56
57
|
evaluator: AnswerCorrectnessEvaluator,
|
|
57
58
|
) -> None:
|
|
59
|
+
csv.field_size_limit(sys.maxsize)
|
|
60
|
+
|
|
58
61
|
with open(input_tsv_file_path, encoding="utf-8") as f:
|
|
59
62
|
reader = csv.DictReader(f, delimiter="\t")
|
|
60
63
|
rows = [row for row in reader]
|
|
@@ -73,13 +76,18 @@ async def evaluate_and_write(
|
|
|
73
76
|
raise ValueError("Unexpected input format!")
|
|
74
77
|
|
|
75
78
|
try:
|
|
76
|
-
|
|
79
|
+
correctness = await evaluator.evaluate_answer(
|
|
77
80
|
row["Question"],
|
|
78
81
|
row["Reference answer"],
|
|
79
82
|
row["Actual answer"]
|
|
80
83
|
)
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
writer.writerow([
|
|
85
|
+
correctness.reference_claims_count,
|
|
86
|
+
correctness.actual_claims_count,
|
|
87
|
+
correctness.matching_claims_count,
|
|
88
|
+
correctness.reason,
|
|
89
|
+
"",
|
|
90
|
+
])
|
|
83
91
|
except Exception as exc:
|
|
84
92
|
writer.writerow(["", "", "", "", str(exc)])
|
|
85
93
|
f.flush()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Below are a query, a reference response and a candidate response to it.
|
|
2
|
+
1. Extract all claims from each response.
|
|
3
|
+
2. Find matching claims between responses. Matching claims have the same meaning and details such as subjects, names, locations, amounts, IDs, commands and paths.
|
|
4
|
+
3. Output one JSON object only. Do not include Markdown, code fences, or any text outside the JSON object.
|
|
5
|
+
|
|
6
|
+
# Query
|
|
7
|
+
{question}
|
|
8
|
+
|
|
9
|
+
# Reference response
|
|
10
|
+
{reference_answer}
|
|
11
|
+
|
|
12
|
+
# Candidate response
|
|
13
|
+
{actual_answer}
|
|
14
|
+
|
|
15
|
+
# JSON fields
|
|
16
|
+
* reference_claims_count: Count of reference response claims
|
|
17
|
+
* actual_claims_count: Count of candidate response claims
|
|
18
|
+
* matching_claims_count: Count of matching claims
|
|
19
|
+
* reason: Explanation of the counts in English
|
|
20
|
+
|
|
21
|
+
# Value checks
|
|
22
|
+
* 1 <= reference_claims_count, actual_claims_count
|
|
23
|
+
* 0 <= matching_claims_count <= reference_claims_count, actual_claims_count
|
|
24
|
+
|
|
25
|
+
# Output format
|
|
26
|
+
{{
|
|
27
|
+
"reference_claims_count": 1,
|
|
28
|
+
"actual_claims_count": 1,
|
|
29
|
+
"matching_claims_count": 1,
|
|
30
|
+
"reason": "The reference and candidate responses contain the same single claim."
|
|
31
|
+
}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "graphrag-eval"
|
|
3
|
-
version = "6.
|
|
3
|
+
version = "6.5.0"
|
|
4
4
|
description = "For assessing question answering systems' final answers and intermediate steps, against a given set of questions, reference answers and steps."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Philip Ganchev", email = "philip.ganchev@graphwise.ai" },
|
|
@@ -17,23 +17,23 @@ repository = "https://github.com/Ontotext-AD/graphrag-eval"
|
|
|
17
17
|
[tool.poetry.dependencies]
|
|
18
18
|
python-dateutil = "2.9.0.post0"
|
|
19
19
|
ragas = { version = "0.4.3", optional = true }
|
|
20
|
-
pydantic = "2.
|
|
20
|
+
pydantic = "2.14.1"
|
|
21
21
|
|
|
22
22
|
[tool.poetry.extras]
|
|
23
23
|
llm = ["ragas", "litellm", "pyyaml"]
|
|
24
24
|
|
|
25
25
|
[tool.poetry.group.llm.dependencies]
|
|
26
26
|
ragas = "0.4.3"
|
|
27
|
-
litellm = "1.
|
|
27
|
+
litellm = "1.105.1"
|
|
28
28
|
pyyaml = "6.0.3"
|
|
29
29
|
|
|
30
30
|
[tool.poetry.group.llm]
|
|
31
31
|
optional = true
|
|
32
32
|
|
|
33
33
|
[tool.poetry.group.test.dependencies]
|
|
34
|
-
pytest = "<10,>=9.
|
|
35
|
-
pytest-asyncio = "1.
|
|
36
|
-
pytest-cov = "<8,>=7"
|
|
34
|
+
pytest = "<10,>=9.1.1"
|
|
35
|
+
pytest-asyncio = "<2,>=1.4.0"
|
|
36
|
+
pytest-cov = "<8,>=7.1.0"
|
|
37
37
|
jsonlines = "4.0.0"
|
|
38
38
|
pyyaml = "6.0.3"
|
|
39
39
|
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
Below are a query, a reference response and a candidate response to it.
|
|
2
|
-
1. Extract all claims from each response
|
|
3
|
-
2. Find matching claims between responses. Matching claims have the same meaning and details such as subjects, names, locations, amounts, IDs, commands and paths.
|
|
4
|
-
3. Output the values listed below (all and only those).
|
|
5
|
-
|
|
6
|
-
# Query
|
|
7
|
-
{question}
|
|
8
|
-
|
|
9
|
-
# Reference response
|
|
10
|
-
{reference_answer}
|
|
11
|
-
|
|
12
|
-
# Candidate response
|
|
13
|
-
{actual_answer}
|
|
14
|
-
|
|
15
|
-
# Output values
|
|
16
|
-
* v1: Count of reference response claims
|
|
17
|
-
* v2: Count of candidate response claims
|
|
18
|
-
* v3: Count of matching claims
|
|
19
|
-
* v4: Explanation of v1-v3 in English
|
|
20
|
-
|
|
21
|
-
# Value checks
|
|
22
|
-
* 1 <= v1, v2
|
|
23
|
-
* 0 <= v3 <= v1, v2
|
|
24
|
-
|
|
25
|
-
# Output format
|
|
26
|
-
<v1><tab><v2><tab><v3><tab><v4>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|