-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevaluations.py
More file actions
179 lines (152 loc) · 6.76 KB
/
Copy pathevaluations.py
File metadata and controls
179 lines (152 loc) · 6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import json
import os
from datetime import datetime
from loguru import logger
from openai import APIConnectionError, RateLimitError
from tqdm import tqdm
from transformers import LlamaTokenizerFast
CACHE_DIR = os.path.join("hf_cache")
tokenizer = LlamaTokenizerFast.from_pretrained("meta-llama/Llama-3.1-70B", cache_dir=CACHE_DIR)
INSTRUCTIONS = """
# Task:
You are given a Question, a model Prediction, and a list of Ground Truth answers, judge whether the model Prediction matches any answer from the list of Ground Truth answers. Follow the instructions step by step to make a judgement.
1. If the model prediction matches any provided answers from the Ground Truth Answer list, "Accuracy" should be "True"; otherwise, "Accuracy" should be "False".
2. If the model prediction says that it couldn't answer the question or it doesn't have enough information, "Accuracy" should always be "False".
3. If the Ground Truth is "invalid question", "Accuracy" is "True" only if the model prediction is exactly "invalid question".
4. Determine the Hits@1 accuracy of the model by comparing the model prediction with the first element relevant to answer the question.
# Output:
Respond with only JSON string with an "Accuracy" field which is "True" or "False" and a second key with a "Hits1" field.
"""
IN_CONTEXT_EXAMPLES = """
# Examples:
Question: how many seconds is 3 minutes 15 seconds?
Ground truth: ["195 seconds"]
Prediction: 3 minutes 15 seconds is 195 seconds.
Accuracy: True
Question: Who authored The Taming of the Shrew (published in 2002)?
Ground truth: ["William Shakespeare", "Roma Gill"]
Prediction: The author to The Taming of the Shrew is Roma Shakespeare.
Accuracy: False
Question: Who played Sheldon in Big Bang Theory?
Ground truth: ["Jim Parsons", "Iain Armitage"]
Prediction: I am sorry I don't know.
Accuracy: False
"""
def attempt_api_call(client, model_name, messages, max_retries=10):
"""Attempt an API call with retries upon encountering specific errors."""
# todo: add default response when all efforts fail
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model_name,
messages=messages,
response_format={"type": "json_object"},
)
return response.choices[0].message.content
except (APIConnectionError, RateLimitError):
logger.warning(f"API call failed on attempt {attempt + 1}, retrying...")
except Exception as e:
logger.error(f"Unexpected error: {e}")
break
return None
def parse_response(resp: str):
"""Pass auto-eval output from the evaluator."""
try:
resp = resp.lower()
model_resp = json.loads(resp)
answer = -1
if "accuracy" in model_resp and (
(model_resp["accuracy"] is True)
or (
isinstance(model_resp["accuracy"], str)
and model_resp["accuracy"].lower() == "true"
)
):
answer = 1
else:
raise ValueError(f"Could not parse answer from response: {model_resp}")
return answer
except:
return -1
def log_response(messages, response, output_directory="api_responses"):
"""Save the response from the API to a file."""
os.makedirs(output_directory, exist_ok=True)
file_name = datetime.now().strftime("%d-%m-%Y-%H-%M-%S.json")
file_path = os.path.join(output_directory, file_name)
with open(file_path, "w") as f:
json.dump({"messages": messages, "response": response}, f)
def trim_predictions_to_max_token_length(prediction):
"""Trims prediction output to 75 tokens using Llama2 tokenizer"""
max_token_length = 256
tokenized_prediction = tokenizer.encode(prediction)
trimmed_tokenized_prediction = tokenized_prediction[1: max_token_length + 1]
trimmed_prediction = tokenizer.decode(trimmed_tokenized_prediction)
return trimmed_prediction
def evaluate_predictions(queries, ground_truths, predictions, evaluation_model_name, client):
n_miss, n_correct, n_correct_exact = 0, 0, 0
system_message = INSTRUCTIONS + IN_CONTEXT_EXAMPLES
for _idx, pkey in enumerate(tqdm(
predictions.keys(), total=len(predictions.keys()), desc="Evaluating Predictions"
)):
query = queries[pkey]
ground_truth = ground_truths[pkey].strip()
prediction = predictions[pkey]
print('Evaluating prediction:', prediction)
# parse prediction
if not prediction:
prediction = ''
elif isinstance(prediction, list):
if all(isinstance(el, str) for el in prediction):
prediction = ', '.join(prediction)
else:
try:
# try to convert...
prediction = [str(el) for el in prediction]
prediction = ', '.join(prediction)
except Exception:
prediction = ''
elif isinstance(prediction, int):
try:
prediction = str(prediction)
except Exception:
prediction = ''
# trim prediction to 75 tokens using Llama2 tokenizer
prediction = trim_predictions_to_max_token_length(prediction)
prediction = prediction.strip()
ground_truth_lowercase = ground_truth.lower()
prediction_lowercase = prediction.lower()
if "could not answer this question as no relevant information about the question was available in the knowledge base" in prediction_lowercase:
n_miss += 1
continue
elif prediction_lowercase == ground_truth_lowercase:
n_correct_exact += 1
n_correct += 1
continue
messages = [
{"role": "system", "content": system_message},
{
"role": "user",
"content": f"Question: {query}\n Ground truth: {ground_truth}\n Prediction: {prediction}\n",
},
]
# if previous evaluation attempts fail...
response = attempt_api_call(client, evaluation_model_name, messages)
if response:
log_response(messages, response)
eval_res = parse_response(response)
if eval_res == 1:
n_correct += 1
n = len(predictions.keys())
results = {
"score": (2 * n_correct + n_miss) / n - 1,
"exact_accuracy": n_correct_exact / n,
"accuracy": n_correct / n,
"hallucination": (n - n_correct - n_miss) / n,
"missing": n_miss / n,
"n_miss": n_miss,
"n_correct": n_correct,
"n_correct_exact": n_correct_exact,
"total": n,
}
logger.info(results)
return results