-
Notifications
You must be signed in to change notification settings - Fork 146
Expand file tree
/
Copy path_generative_judge.py
More file actions
239 lines (176 loc) · 8.36 KB
/
Copy path_generative_judge.py
File metadata and controls
239 lines (176 loc) · 8.36 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
POINTWISE_J1_PROMPT = """
You are given a user question and a response from an AI assistant. Your task is to act as an impartial judge and evaluate how well the response fulfills the user's instructions. You will be shown multiple responses to the same prompt, but only one at a time. Evaluate each response independently.
Think carefully about how to assess the quality of the response, and enclose your reasoning within <think> and </think> tags. Your reasoning should include your evaluation criteria, a clear understanding of what an ideal response would look like for this particular question, and a concrete example of such an ideal or reference answer if possible. Then compare the assistant's response to your ideal or reference answer, explaining how it aligns with or deviates from your expectations. Be specific and avoid vague or overly general judgments. Remain as objective as possible.
Finally, assign the assistant's response a score from 0 to 10, using either an integer or a decimal with up to 0.1 precision. A higher score should indicate a higher-quality response. Enclose the score within <score> and </score> tags.
Format your output like this:
<think> your_thinking_process </think>
<score> your_score </score>
Below are the user's question and the assistant's response:
[User Question]
{instruction}
[The Start of the Assistant's Answer]
{response}
[The End of the Assistant's Answer]
"""
PAIRWISE_J1_PROMPT = """
You are given a user question and two responses from two AI assistants. Your task is to act as an impartial judge and evaluate which response better follows the user's instructions and provides a higher-quality answer.
First, provide your reasoning within <think> and </think> tags. This should include your evaluation criteria for a high-quality response, a detailed comparison of the two responses, and when helpful, a reference answer as part of your evaluation. Be explicit in your thought process, referencing your criteria and explaining how each response aligns with or deviates from them.
Avoid any position biases and ensure that the order in which the responses were presented does not influence your decision. Do not allow the length of the responses to influence your evaluation. Do not favor certain names of the assistants. Be as objective as possible.
Finally, provide your verdict within <answer> and </answer> tags, strictly following this format:
- <answer> [[A]] </answer> if Assistant A is better
- <answer> [[B]] </answer> if Assistant B is better
Below are the user's question and the two responses:
[User Question]
{instruction}
[The Start of Assistant A's Answer]
{response_A}
[The End of Assistant A's Answer]
[The Start of Assistant B's Answer]
{response_B}
[The End of Assistant B's Answer]
"""
PAIRWISE_WITH_SCORES_J1_PROMPT = """
You are given a user question and two responses from two AI assistants. Your task is to act as an impartial judge and evaluate which response better follows the user's instructions and provides a higher-quality answer.
First, provide your reasoning within <think> and </think> tags. This should include your evaluation criteria for a high-quality response, a detailed comparison of the two responses, and when helpful, a reference answer as part of your evaluation. Be explicit in your thought process, referencing your criteria and explaining how each response aligns with or deviates from them.
Avoid any position biases and ensure that the order in which the responses were presented does not influence your decision. Do not allow the length of the responses to influence your evaluation. Do not favor certain names of the assistants. Be as objective as possible.
Finally, assign the assistant's response a score from 0 to 10, using either an integer or a decimal with up to 0.1 precision, with a higher score indicating a higher-quality response that better satisfies the criteria. Enclose the scores within the tags <score_A> </score_A>, and <score_B> </score_B>.
Format your output like this:
<think> your_thinking_process </think>
<score_A> your_score_a </score_A> <score_B> your_score_b </score_B>
Below are the user's question and the two responses:
[User Question]
{instruction}
[The Start of Assistant A's Answer]
{response_A}
[The End of Assistant A's Answer]
[The Start of Assistant B's Answer]
{response_B}
[The End of Assistant B's Answer]
"""
from abc import ABC, abstractmethod
from typing_extensions import override
from fairseq2.logging import log
import re
class JudgmentExtractorHandler(ABC):
@abstractmethod
def create(self): ...
@property
@abstractmethod
def name(self) -> str: ...
@property
@abstractmethod
def config_kls(self) -> type[object]: ...
'''
All judgment extractors are expected to:
(1) define their judgment prompt
(2) implement their judgment (i.e., scores, preferences, etc) extraction logic from the CoTs
(3) implement their aggregation logic over judgments, if sampling multiple CoTs
'''
class JudgmentExtractor(ABC):
@abstractmethod
def prompt(self): ...
@abstractmethod
def extract(self): ...
@abstractmethod
def aggregate(self, judgments): ...
class J1PointwiseExtractorHandler(JudgmentExtractorHandler):
def __init__(self):
pass
@override
def create(self):
return J1PointwiseExtractor()
@property
@override
def name(self):
return "j1_pointwise_extractor"
@property
@override
def config_kls(self):
return None
class J1PointwiseExtractor(JudgmentExtractor):
def __init__(self):
pass
@override
def prompt(self):
return POINTWISE_J1_PROMPT
@override
def extract(self, generation):
matches = re.findall(
r"<score>\s*([0-9]+(?:\.[0-9])?)\s*(?:/10)?\s*</score>", generation
)
if matches and float(matches[-1].strip()) > 10.0:
log.info(f"Judge output = {generation}")
return float(matches[-1].strip()) if matches else 0.0
@override
def aggregate(self, judgments):
avg_score = 0.0
for score in judgments:
avg_score += score
return round(avg_score/len(judgments), 4)
class J1PairwiseScoreExtractorHandler(JudgmentExtractorHandler):
def __init__(self):
pass
@override
def create(self):
return J1PairwiseScoreExtractor()
@property
@override
def name(self):
return "j1_pairwise_score_extractor"
@property
@override
def config_kls(self):
return None
class J1PairwiseScoreExtractor(JudgmentExtractor):
def __init__(self):
pass
@override
def prompt(self):
return PAIRWISE_WITH_SCORES_J1_PROMPT
@override
def extract(self, generation):
score_a_matches = re.findall(r"<score_A>\s*([0-9]+(?:\.[0-9])?)\s*(?:/10)?\s*</score_A>", generation)
score_b_matches = re.findall(r"<score_B>\s*([0-9]+(?:\.[0-9])?)\s*(?:/10)?\s*</score_B>", generation)
if score_a_matches and score_b_matches:
score_a = score_a_matches[-1]
score_b = score_b_matches[-1]
if float(score_a.strip()) > 10.0 or float(score_b.strip()) > 10.0:
log.info(f"Judge output = {generation}")
return (float(score_a.strip()), float(score_b.strip()))
else:
return (0.0, 0.0)
@override
def aggregate(self, judgments):
avg_score = (0.0, 0.0)
for score in judgments:
avg_score = (avg_score[0]+score[0], avg_score[1]+score[1])
return (round(avg_score[0]/len(judgments), 4), round(avg_score[1]/len(judgments), 4))
class J1PairwisePreferenceExtractorHandler(JudgmentExtractorHandler):
def __init__(self):
pass
@override
def create(self):
return J1PairwisePreferenceExtractor()
@property
@override
def name(self):
return "j1_pairwise_preference_extractor"
@property
@override
def config_kls(self):
return None
class J1PairwisePreferenceExtractor(JudgmentExtractor):
def __init__(self):
pass
@override
def prompt(self):
return PAIRWISE_J1_PROMPT
@override
def extract(self, generation):
matches = list(
re.findall(r"<answer>\s*\[\[(A|B)\]\]\s*</answer>", generation.strip())
)
return matches[-1].strip() if matches else None
@override
def aggregate(self, judgments):
pass