1111from dataflow import get_logger
1212from typing import Literal , Union
1313import pandas as pd
14- import numpy as np
1514import time
16- import os # 添加os模块导入
15+ import os
1716import re
18- import json
1917import json5
2018
2119@prompt_restrict (
@@ -32,7 +30,7 @@ def __init__(self,
3230 llm_serving : LLMServingABC = None ,
3331 prompt_template : Union [AnswerJudgePromptQuestion , AnswerJudgeMultipleQuestionsPrompt , DIYPromptABC ] = AnswerJudgePromptQuestion ,
3432 support_subquestions : bool = False ,
35- keep_all_samples : bool = False
33+ keep_all_samples : bool = False ,
3634 ):
3735
3836 if eval_result_path is None :
@@ -44,7 +42,7 @@ def __init__(self,
4442 self .empty_responses_count = 0 # 添加空响应计数器
4543 self .keep_all_samples = keep_all_samples
4644 self .support_subquestions = support_subquestions
47-
45+
4846 if compare_method == "match" :
4947 self .compare = self .math_verify_compare
5048 unit_manager = UnitTextManager ()
@@ -203,7 +201,136 @@ def statistic(self, file_name_prefix: str, dataframe: pd.DataFrame, compare_meth
203201 self .logger .success (f"Statistics saved to { self .eval_result_path } " )
204202
205203 return stats_df
206-
204+
205+ def _get_required_columns (
206+ self ,
207+ input_test_answer_key : str ,
208+ input_gt_answer_key : str ,
209+ input_question_key : str ,
210+ ) -> list [str ]:
211+ required_columns = [input_test_answer_key , input_gt_answer_key ]
212+ if self .compare_method == "semantic" :
213+ required_columns .append (input_question_key )
214+ return required_columns
215+
216+ def _run_match_evaluation (
217+ self ,
218+ storage : DataFlowStorage ,
219+ dataframe : pd .DataFrame ,
220+ required_columns : list [str ],
221+ ) -> list [str ]:
222+ for row_index in dataframe .index :
223+ answer = dataframe .at [row_index , self .test_answer_key ]
224+ ground_truth = dataframe .at [row_index , self .gt_answer_key ]
225+ final_answer = self .answer_extractor .extract_answer (answer , None )
226+ dataframe .at [row_index , "answer_match_result" ] = self .compare (
227+ final_answer ,
228+ ground_truth ,
229+ )
230+
231+ storage .write (dataframe )
232+ self .statistic (storage .file_name_prefix , dataframe , self .compare_method )
233+ return required_columns + ["answer_match_result" ]
234+
235+ def _build_semantic_inputs (self , valid_rows : pd .DataFrame ) -> list [str ]:
236+ return [
237+ self .prompt_template .build_prompt (
238+ question = row [self .question_key ],
239+ answer = row [self .test_answer_key ],
240+ reference_answer = row [self .gt_answer_key ],
241+ )
242+ for _ , row in valid_rows .iterrows ()
243+ ]
244+
245+ def _handle_missing_reference_answers (
246+ self ,
247+ storage : DataFlowStorage ,
248+ dataframe : pd .DataFrame ,
249+ required_columns : list [str ],
250+ skipped_count : int ,
251+ ) -> list [str ]:
252+ self .logger .warning (
253+ "No valid samples with reference answers found. All samples skipped."
254+ )
255+ output_dataframe = (
256+ dataframe if self .keep_all_samples else dataframe .iloc [0 :0 ].copy ()
257+ )
258+ output_file = storage .write (output_dataframe )
259+ self .logger .info (
260+ f"Dataframe saved to { output_file } . Skipped { skipped_count } samples "
261+ "due to missing reference answers."
262+ )
263+ return required_columns + ["answer_match_result" ]
264+
265+ def _apply_subquestion_results (
266+ self ,
267+ dataframe : pd .DataFrame ,
268+ valid_rows : pd .DataFrame ,
269+ responses : list [str ],
270+ results : list [str ],
271+ ) -> None :
272+ for row_index , response , result in zip (valid_rows .index , responses , results ):
273+ correct_answer_count , total_subquestions = map (int , result .split ("/" ))
274+ dataframe .at [row_index , "correct_answer_num" ] = correct_answer_count
275+ dataframe .at [row_index , "total_subquestions" ] = total_subquestions
276+ dataframe .at [row_index , "answer_match_result" ] = (
277+ correct_answer_count == total_subquestions and total_subquestions > 0
278+ )
279+ dataframe .at [row_index , "response_evaluation" ] = response
280+
281+ def _apply_semantic_results (
282+ self ,
283+ dataframe : pd .DataFrame ,
284+ valid_rows : pd .DataFrame ,
285+ responses : list [str ],
286+ results : list ,
287+ ) -> None :
288+ if self .support_subquestions :
289+ self ._apply_subquestion_results (dataframe , valid_rows , responses , results )
290+ return
291+
292+ for row_index , result in zip (valid_rows .index , results ):
293+ dataframe .at [row_index , "answer_match_result" ] = result
294+
295+ def _run_semantic_evaluation (
296+ self ,
297+ storage : DataFlowStorage ,
298+ dataframe : pd .DataFrame ,
299+ required_columns : list [str ],
300+ ) -> list [str ]:
301+ empty_reference_mask = dataframe [self .gt_answer_key ].isna () | (
302+ dataframe [self .gt_answer_key ] == ""
303+ )
304+ valid_rows = dataframe [~ empty_reference_mask ]
305+ skipped_count = int (empty_reference_mask .sum ())
306+
307+ if valid_rows .empty :
308+ return self ._handle_missing_reference_answers (
309+ storage ,
310+ dataframe ,
311+ required_columns ,
312+ skipped_count ,
313+ )
314+
315+ inputs = self ._build_semantic_inputs (valid_rows )
316+ responses = self .llm_serving .generate_from_input (
317+ user_inputs = inputs ,
318+ system_prompt = self .system_prompt ,
319+ )
320+ response_count = 0 if responses is None else len (responses )
321+ if response_count != len (inputs ):
322+ raise RuntimeError (
323+ "LLM serving returned an unexpected number of responses: "
324+ f"expected { len (inputs )} , got { response_count } ."
325+ )
326+
327+ results = [self .ResolveResponse (response ) for response in responses ]
328+ self ._apply_semantic_results (dataframe , valid_rows , responses , results )
329+ storage .write (dataframe )
330+ self .statistic (storage .file_name_prefix , dataframe , self .compare_method )
331+ self .empty_responses_count = 0
332+ return required_columns + ["answer_match_result" ]
333+
207334 def run (
208335 self ,
209336 storage :DataFlowStorage ,
@@ -217,93 +344,15 @@ def run(
217344 self .question_key = input_question_key
218345
219346 dataframe = storage .read ("dataframe" )
220- dataframe ['answer_match_result' ] = False
221- answers = dataframe [self .test_answer_key ]
222- ground_truths = dataframe [self .gt_answer_key ]
223-
224- if self .compare_method == "match" :
225- required_columns = [input_test_answer_key , input_gt_answer_key ]
226- if self .check_column (
227- required_columns = required_columns ,
228- dataframe = dataframe
229- ) is False :
230- return required_columns
231-
232- for i in range (len (answers )):
233- final_answer = self .answer_extractor .extract_answer (answers [i ], None )
234- if self .compare (final_answer , ground_truths [i ]):
235- dataframe .at [i , 'answer_match_result' ] = True
236- else :
237- dataframe .at [i , 'answer_match_result' ] = False
238-
239- output_file = storage .write (dataframe )
240-
241- # 生成统计信息并直接写入JSON文件
242- stats = self .statistic (storage .file_name_prefix , dataframe , self .compare_method )
243-
244- return [self .test_answer_key , self .gt_answer_key , 'answer_match_result' ]
245- else :
246- required_columns = [input_test_answer_key , input_gt_answer_key , input_question_key ]
247- if self .check_column (
248- required_columns = required_columns ,
249- dataframe = dataframe
250- ) is False :
251- return required_columns
252-
253- empty_reference_mask = dataframe [input_gt_answer_key ].isna () | (dataframe [input_gt_answer_key ] == '' )
254- skipped_rows = dataframe [empty_reference_mask ]
255- valid_rows = dataframe [~ empty_reference_mask ]
256- skipped_count = len (skipped_rows )
257-
258- if len (valid_rows ) == 0 :
259- self .logger .warning ("No valid samples with reference answers found. All samples skipped." )
260- if self .keep_all_samples :
261- output_file = storage .write (dataframe ) # 保留所有行,但answer_match_result都为False
262- else :
263- output_file = storage .write (pd .DataFrame (columns = dataframe .columns )) # 不保留任何行
264- self .logger .info (f"Dataframe saved to { output_file } . Skipped { skipped_count } samples due to missing reference answers." )
265- return required_columns + ['answer_match_result' ]
266-
267- # 只对有参考答案的行构建提示词并调用LLM
268- inputs = [self .prompt_template .build_prompt (
269- question = row [input_question_key ],
270- answer = row [input_test_answer_key ],
271- reference_answer = row [input_gt_answer_key ]
272- ) for _ , row in valid_rows .iterrows ()]
273-
274- responses = self .llm_serving .generate_from_input (user_inputs = inputs , system_prompt = self .system_prompt )
275-
276- # if self.support_subquestions:
277- # # 每个response是一个列表,连接一个长列表,比如[["true", "false"], ["true"]] -> ["true", "false", "true"]
278- # responses = [item for sublist in responses for item in sublist]
279-
280- results = [self .ResolveResponse (response ) for response in responses ]
281-
282- # 创建结果掩码,与valid_rows长度相同
283- result_mask = np .array (results , dtype = bool )
284-
285- # 更新有效行的answer_match_result
286- valid_indices = valid_rows .index
287- if not self .support_subquestions :
288- for i , idx in enumerate (valid_indices ):
289- dataframe .at [idx , 'answer_match_result' ] = results [i ]
290- else :
291- for i , idx in enumerate (valid_indices ):
292- correct_answer_num = int (results [i ].split ('/' )[0 ])
293- total_subquestions = int (results [i ].split ('/' )[1 ])
294- dataframe .at [idx , 'correct_answer_num' ] = correct_answer_num
295- dataframe .at [idx , 'total_subquestions' ] = total_subquestions
296- dataframe .at [idx , 'answer_match_result' ] = (correct_answer_num == total_subquestions ) and (total_subquestions > 0 ) # 全对为True,否则为False
297- dataframe .at [idx , 'response_evaluation' ] = responses [i ] # 保存LLM的原始响应内容
298-
299- output_file = storage .write (dataframe )
300-
301- # 生成统计信息并直接写入JSON文件
302- stats = self .statistic (storage .file_name_prefix , dataframe , self .compare_method )
303-
304- # 重置空响应计数器
305- self .empty_responses_count = 0
306-
307- return [input_test_answer_key , input_gt_answer_key , input_question_key , 'answer_match_result' ]
347+ required_columns = self ._get_required_columns (
348+ input_test_answer_key ,
349+ input_gt_answer_key ,
350+ input_question_key ,
351+ )
352+ if not self .check_column (required_columns , dataframe ):
353+ return required_columns
308354
309-
355+ dataframe ["answer_match_result" ] = False
356+ if self .compare_method == "match" :
357+ return self ._run_match_evaluation (storage , dataframe , required_columns )
358+ return self ._run_semantic_evaluation (storage , dataframe , required_columns )
0 commit comments