Newer
Older
MedSegAgentWebApp / evaluate.py
@Haoyu Wang Haoyu Wang on 4 Jun 2025 11 KB FEAT: update MedSegAgent V1 Core (#1)
import argparse
import json
import logging
import colorlog
import os
import os.path as osp
from datetime import datetime

from utils.chat_utils import chat_with_llm
from utils.metric import dataset_hit_rate, all_dataset_found_rate, any_dataset_found_rate
from utils.reply_utils import *

import time


def score_the_answer(gt_answer, llm_answer):
    return {
        "hit_rate": dataset_hit_rate(gt_answer, llm_answer),
        "strict_hit_rate": all_dataset_found_rate(gt_answer, llm_answer),
        "soft_hit_rate": any_dataset_found_rate(gt_answer, llm_answer),
    }


def run_tests_from_jsonl(file_path,
                         llm_func,
                         result_file,
                         resume=False,
                         state_filename='task_state.json'):
    final_score, total_score = 0, 0
    final_strict_score, final_soft_score = 0, 0
    start_line_id = 0
    evaluation_results = dict()

    # try to resume state
    if resume:
        logging.info("[Results] resume from state")
        state = load_state(state_filename=state_filename)
        if state:
            start_line_id = state['current_line']
            final_score = state['final_score']
            final_strict_score = state['final_strict_score']
            final_soft_score = state['final_soft_score']
            total_score = state['total_score']
            evaluation_results = state['evaluation_results']

    with open(file_path, 'r') as file:
        for line_id, line in enumerate(file):
            line_id += 1
            if line_id <= start_line_id:
                continue

            test_case = json.loads(line)
            query = test_case['query']
            gt_answer = extract_ground_truth(test_case)

            res_dict = llm_func(query)
            evaluation_results[line_id] = res_dict
            try:
                res = res_dict['reply']
            except KeyError:
                res = res_dict
            try:
                chat_history = res_dict['chat_history']
            except KeyError:
                chat_history = ""
            formatted_history = '\n'.join([
                chat['role'] + '(' + chat.get('name', "unamed") + "): " + chat['content']
                for chat in chat_history
            ])
            logging.info(f"[{line_id}] Chat History:\n{formatted_history}")
            # llm_answer = extract_llm_answer(res)
            llm_answer = res
            case_score = score_the_answer(gt_answer, llm_answer)

            hit_rate = case_score['hit_rate']
            strict_hit_rate = case_score['strict_hit_rate']
            soft_hit_rate = case_score['soft_hit_rate']

            final_score += hit_rate
            final_strict_score += strict_hit_rate
            final_soft_score += soft_hit_rate
            total_score += 1.0

            if hit_rate == 1.0:  # answer correctly
                logging.info(
                    f"[{line_id}] Correct! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )
                print(
                    f"[{line_id}] Correct! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )
            else:
                logging.error(
                    f"[{line_id}] Wrong! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )
                print(
                    f"[{line_id}] Wrong! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )

            # time.sleep(5)

            # save the state
            state = {
                'current_line': line_id,
                'final_score': final_score,
                'final_strict_score': final_strict_score,
                'final_soft_score': final_soft_score,
                'total_score': total_score,
                'evaluation_results': evaluation_results,
            }
            save_state(state, state_filename=state_filename)

    logging.info(f"[Summary] [Datasets Hit Rate] final score {final_score:.2f} / {total_score}")
    logging.info(
        f"[Summary] [All Dataset Found] final score {final_strict_score:.2f} / {total_score}")
    logging.info(
        f"[Summary] [Any Dataset Found] final score {final_soft_score:.2f} / {total_score}")

    if not args.skip_results_saving:
        logging.info(f"[Results] result saved to {result_file}")
        with open(result_file, 'w') as f:
            json.dump(evaluation_results, f, indent=4)
    else:
        logging.info("[Results] skip results saving")

    if os.path.exists(state_filename):
        logging.info("[Results] clean state")
        os.remove(state_filename)

    return {
        'final_score': final_score,
        'final_strict_score': final_strict_score,
        'final_soft_score': final_soft_score,
        'total_score': total_score,
    }


def recompute_metrics_from_file(question_file, result_file):
    final_score, total_score = 0, 0
    final_strict_score, final_soft_score = 0, 0
    evaluation_results = json.load(open(result_file, "r"))

    question_numbers = 0
    with open(question_file, 'r') as file:
        for line_id, line in enumerate(file):
            question_numbers += 1

    if (len(evaluation_results) != question_numbers):
        logging.error("")
        return -1

    with open(question_file, 'r') as file:
        for line_id, line in enumerate(file):
            line_id += 1

            test_case = json.loads(line)
            gt_answer = extract_ground_truth(test_case)

            res_dict = evaluation_results[str(line_id)]
            res = res_dict['reply']
            chat_history = res_dict['chat_history']
            formatted_history = '\n'.join(
                [chat['role'] + ": " + chat['content'] for chat in chat_history])
            logging.info(f"[{line_id}] Chat History:\n{formatted_history}")
            llm_answer = extract_llm_answer(res)

            case_score = score_the_answer(gt_answer, llm_answer)

            hit_rate = case_score['hit_rate']
            strict_hit_rate = case_score['strict_hit_rate']
            soft_hit_rate = case_score['soft_hit_rate']

            final_score += hit_rate
            final_strict_score += strict_hit_rate
            final_soft_score += soft_hit_rate
            total_score += 1.0

            if hit_rate == 1.0:  # answer correctly
                logging.info(
                    f"[{line_id}] Correct! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )
            else:
                logging.error(
                    f"[{line_id}] Wrong! (hit_rate: {hit_rate}, final: {final_score})\nExpected answer:\n{gt_answer}\nLLM:\n{llm_answer}"
                )

    logging.warning(f"[Summary] [Dataset  Hit Rate] final score {final_score:.2f} / {total_score}")
    logging.warning(
        f"[Summary] [Dataset All Found] final score {final_strict_score:.2f} / {total_score}")
    logging.warning(
        f"[Summary] [Dataset Any Found] final score {final_soft_score:.2f} / {total_score}")

    return {
        'final_score': final_score,
        'final_strict_score': final_strict_score,
        'final_soft_score': final_soft_score,
        'total_score': total_score,
    }


def get_timestamped_log_file(pattern, file_path, tag):
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    base_name = osp.basename(file_path).replace('.jsonl', '')
    os.makedirs("results", exist_ok=True)
    return osp.join("results", f'{pattern}_{base_name}_{tag}_{timestamp}.log')


def main(test_file_path, test_pattern, model, tag, resume, log_to_file):
    tag = osp.basename(model)
    log_outfile = get_timestamped_log_file(test_pattern, test_file_path, tag)
    if (args.recompute_metrics_from_file):
        log_outfile = args.recompute_metrics_from_file.replace(".json", "_recompute.log")
    if resume:
        state = load_state()
        if state:
            log_outfile = log_outfile.replace(".log", "_resume.log")

    if log_to_file:
        print(f"saving file to {log_outfile}")
        logging.basicConfig(filename=log_outfile,
                            level=logging.INFO,
                            format='%(asctime)s - %(levelname)s - %(message)s')
    else:
        handler = colorlog.StreamHandler()
        log_colors = {
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'bold_red',
        }
        formatter = colorlog.ColoredFormatter(
            '%(log_color)s%(asctime)s - %(levelname)s - %(message)s', log_colors=log_colors)
        handler.setFormatter(formatter)
        logging.basicConfig(level=logging.INFO)
        logger = logging.getLogger()
        logger.handlers.clear()  # Clear existing handlers
        logger.addHandler(handler)

    if (args.recompute_metrics_from_file):
        pass
        final_score_dict = recompute_metrics_from_file(
            question_file=test_file_path,
            result_file=args.recompute_metrics_from_file,
        )
        print(
            f"recomputed  hit rate: {final_score_dict['final_score']:.2f} / {final_score_dict['total_score']}"
        )
        print(
            f"recomputed all found: {final_score_dict['final_strict_score']:.2f} / {final_score_dict['total_score']}"
        )
        print(
            f"recomputed any found: {final_score_dict['final_soft_score']:.2f} / {final_score_dict['total_score']}"
        )
    else:
        final_score_dict = run_tests_from_jsonl(
            test_file_path,
            llm_func=lambda x: chat_with_llm(test_pattern, x, model=model),
            result_file=log_outfile.replace(".log", ".json"),
            resume=resume)
        print(
            f"hit rate: {final_score_dict['final_score']:.2f} / {final_score_dict['total_score']}")
        print(
            f"all found: {final_score_dict['final_strict_score']:.2f} / {final_score_dict['total_score']}"
        )
        print(
            f"any found: {final_score_dict['final_soft_score']:.2f} / {final_score_dict['total_score']}"
        )


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Process some parameters.')
    parser.add_argument('--recompute_metrics_from_file',
                        type=str,
                        default=None,
                        help='Recompute the metrics without query')
    parser.add_argument('--test_file_path', type=str, required=True, help='Path to the test file')
    parser.add_argument('--test_pattern',
                        type=str,
                        required=True,
                        default="Single_Turn",
                        help='Test pattern')
    parser.add_argument('--model',
                        type=str,
                        required=True,
                        default='gpt-4o-mini-2024-07-18',
                        help='Model name')
    parser.add_argument('--tag', type=str, default=None, required=False, help='Tag name')
    parser.add_argument('--resume', action='store_true', help='Resume flag')
    parser.add_argument('--log_to_file', action='store_true', help='Log to file flag')
    parser.add_argument('--skip_results_saving', action='store_true', help='Log to file flag')
    args = parser.parse_args()

    main(args.test_file_path, args.test_pattern, args.model, args.tag, args.resume,
         args.log_to_file)