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.
Files changed (25) hide show
  1. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/PKG-INFO +2 -2
  2. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/answer_correctness.py +39 -43
  3. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/cli/answer_correctness.py +11 -3
  4. graphrag_eval-6.5.0/graphrag_eval/prompts/template.md +31 -0
  5. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/pyproject.toml +6 -6
  6. graphrag_eval-6.4.0/graphrag_eval/prompts/template.md +0 -26
  7. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/LICENSE +0 -0
  8. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/README.md +0 -0
  9. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/__init__.py +0 -0
  10. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/aggregation.py +0 -0
  11. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/answer_relevance.py +0 -0
  12. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/cli/__init__.py +0 -0
  13. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/custom_evaluation.py +0 -0
  14. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/evaluation.py +0 -0
  15. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/evaluator.py +0 -0
  16. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/llm_factory.py +0 -0
  17. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/__init__.py +0 -0
  18. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/evaluation.py +0 -0
  19. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/iri_discovery.py +0 -0
  20. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_answer.py +0 -0
  21. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_context_ids.py +0 -0
  22. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/retrieval_context_texts.py +0 -0
  23. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/sparql.py +0 -0
  24. {graphrag_eval-6.4.0 → graphrag_eval-6.5.0}/graphrag_eval/steps/timeseries.py +0 -0
  25. {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.4.0
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.12.5)
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
- return (await self.ragas_llm.agenerate(prompt, None)).choices[0].message.content
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
- ) -> tuple[int, int, int, str]:
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
- response_str = await self._agenerate(prompt)
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
- num_ref_claims, num_actual_claims, num_matching_claims, reason = \
111
- await self.evaluate_answer(
112
- reference["question_text"],
113
- reference["reference_answer"],
114
- actual["actual_answer"],
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": num_ref_claims,
118
- "answer_actual_claims_count": num_actual_claims,
119
- "answer_matching_claims_count": num_matching_claims,
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
- num_ref_claims, num_actual_claims, num_matching_claims
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
- vals = await evaluator.evaluate_answer(
79
+ correctness = await evaluator.evaluate_answer(
77
80
  row["Question"],
78
81
  row["Reference answer"],
79
82
  row["Actual answer"]
80
83
  )
81
- vals = vals + ("",)
82
- writer.writerow(vals)
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.4.0"
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.12.5"
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.85.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.0.3"
35
- pytest-asyncio = "1.3.0"
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