# -*- coding: utf-8 -*-

import json
import random
import requests
from tqdm import tqdm
from collections import defaultdict
from openai import OpenAI
import re,os
import numpy as np
import datetime
import threading
import time
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

# 全局参数配置
RETRY_TIMES = 5
TIMEOUT_SECONDS = 300  # 添加超时时间设置
THREAD_TIMEOUT_SECONDS = 600  # 线程超时时间（5分钟）
RETRY_DELAY = 2  # 重试间隔（秒）
missed_ids_lock = threading.Lock()  # 新增锁
missed_ids = set()
thread_status = {}  # 记录线程状态
thread_status_lock = threading.Lock()  # 线程状态锁

# 获取项目根目录路径
PROJECT_ROOT = os.path.join(os.path.dirname(__file__), '..', '..')  # 从 code/evaluation 回到项目根目录

# 临时目录
TEMP_OUTPUT_DIR = os.path.join(PROJECT_ROOT, "data", "temp_results")
os.makedirs(TEMP_OUTPUT_DIR, exist_ok=True)

# ========= 工具函数 =========  ·
def extract_json_block(text):
    """
    提取 text 中第一个 JSON 块（支持嵌套大括号），返回 JSON 字符串
    """
    stack = []
    json_start = None
    json_end = None

    for i, char in enumerate(text):
        if char == '{':
            if not stack:
                json_start = i
            stack.append(char)
        elif char == '}':
            if stack:
                stack.pop()
                if not stack:
                    json_end = i
                    break

    if json_start is not None and json_end is not None:
        json_str = text[json_start:json_end+1]
        return json_str
    else:
        return None

def extract_box_answer(text):
    """
    从纯文本中提取 \\box{} 内的答案内容
    支持多种格式: \\box{A}, \\box{A,B}, \\box{A、B}, \\box{A B}
    """
    # 策略1: 标准的 \box{内容} 格式
    pattern1 = r'\\box\{([^}]+)\}'
    matches = re.findall(pattern1, text)
    
    if matches:
        # 取最后一个匹配（以防有多个box）
        box_content = matches[-1].strip()
        
        # 处理不同的分隔符，统一为列表
        # 支持逗号、顿号、空格分隔
        answers = re.split(r'[,，、\s]+', box_content)
        # 过滤空字符串并去除首尾空格
        answers = [ans.strip() for ans in answers if ans.strip()]
        
        return answers
    
    # 策略2: 可能缺少反斜杠的box{内容}格式
    pattern2 = r'box\{([^}]+)\}'
    matches = re.findall(pattern2, text)
    
    if matches:
        box_content = matches[-1].strip()
        answers = re.split(r'[,，、\s]+', box_content)
        answers = [ans.strip() for ans in answers if ans.strip()]
        return answers
    
    # 策略3: 使用中文括号的格式
    pattern3 = r'\\box\{([^}]+)\}'
    matches = re.findall(pattern3, text)
    
    if matches:
        box_content = matches[-1].strip()
        answers = re.split(r'[,，、\s]+', box_content)
        answers = [ans.strip() for ans in answers if ans.strip()]
        return answers
    
    return []

def extract_loose_answer(text):
    """
    宽松的答案提取函数，用于处理格式不规范的模型输出
    尝试从文本中提取任何A-Z的字母选项
    """
    if not text:
        return []
    
    # 策略1: 尝试找到最后出现的单独字母（可能是最终答案）
    # 查找独立的A-Z字母，优先考虑出现在文本末尾的
    letter_pattern = r'\b([A-Z])\b'
    matches = re.findall(letter_pattern, text)
    if matches:
        # 返回最后出现的字母（通常是最终答案）
        return [matches[-1].lower()]
    
    # 策略2: 查找"答案"、"选择"、"结论"等关键词后的字母
    answer_keywords = ['答案', '选择', '结论', '最终', '因此', '所以', '选项']
    for keyword in answer_keywords:
        pattern = f'{keyword}[是为：:]*\\s*([A-Z])'
        matches = re.findall(pattern, text)
        if matches:
            return [matches[-1].lower()]
    
    # 策略3: 查找任何A-Z字母
    all_letters = re.findall(r'[A-Z]', text)
    if all_letters:
        # 返回最后一个字母
        return [all_letters[-1].lower()]
    
    return []

def extract_json_conclusion(text):
    """
    从文本中提取JSON格式的结论字段
    适配新的baseline输出格式:
    {
        "分析": "...",
        "关键词": ["关键词1", "关键词2", ...],
        "结论": "A~Z中的一个或多个选项"
    }
    """
    try:
        # 先尝试提取JSON块
        json_str = extract_json_block(text)
        if json_str:
            try:
                json_obj = json.loads(json_str)
                if isinstance(json_obj, dict) and "结论" in json_obj:
                    conclusion = json_obj["结论"]
                    if isinstance(conclusion, str) and conclusion.strip():
                        # 从结论字符串中提取字母选项
                        result = normalize_conclusion(conclusion)
                        if result:
                            return result
                    elif isinstance(conclusion, list) and conclusion:
                        # 如果结论已经是列表，直接处理
                        result = normalize_conclusion(conclusion)
                        if result:
                            return result
                    
                    # 如果结论字段为空，尝试从整个文本中宽松提取
                    if not conclusion or (isinstance(conclusion, list) and not conclusion):
                        print(f"[DEBUG] JSON结论字段为空，尝试宽松提取")
                        loose_result = extract_loose_answer(text)
                        if loose_result:
                            return loose_result
                        # 如果还是没有，就返回默认值，避免重试
                        return ["no_answer"]
            except json.JSONDecodeError:
                pass
        
        # 如果JSON提取失败，尝试直接在文本中查找"结论"字段
        # 匹配形如 "结论": "A" 或 "结论":"B,C" 的模式
        conclusion_pattern = r'"结论"\s*:\s*"([^"]+)"'
        matches = re.findall(conclusion_pattern, text)
        if matches:
            # 取最后一个匹配
            conclusion_text = matches[-1].strip()
            return normalize_conclusion(conclusion_text)
        
        # 备用方案：查找 "结论" 后跟冒号的文本行
        lines = text.split('\n')
        for line in lines:
            if '结论' in line and ':' in line:
                # 提取冒号后的内容
                parts = line.split(':', 1)
                if len(parts) > 1:
                    conclusion_text = parts[1].strip().strip('"\'')
                    if conclusion_text:
                        return normalize_conclusion(conclusion_text)
    
    except Exception as e:
        print(f"JSON结论提取失败: {e}")
    
    return []

def get_openai_client(api_key: str) -> OpenAI:
    return OpenAI(
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
    )

def call_qwen_unified(prompt, content_type="text", image_url=None, client=None, model_name="qwen2.5-32b-instruct", api_mode="bailian", api_key=None) -> str:
    """
    统一的API调用函数，支持text和image两种内容类型，以及bailian和idealab两种API模式
    
    Args:
        prompt: 输入的文本提示
        content_type: 内容类型，"text" 或 "image"
        image_url: 图片URL（仅在content_type="image"时使用）
        client: OpenAI客户端对象（仅在api_mode="bailian"时使用）
        model_name: 模型名称
        api_mode: API调用模式，"bailian" 或 "idealab"
    
    Returns:
        str: API返回的文本内容
    """
    try:
        if api_mode == "bailian":
            # 使用bailian模式（当前的dashscope调用方式）
            if client is None:
                raise ValueError("bailian模式需要提供client参数")
            
            if content_type == "text":
                # 纯文本调用
                response = client.chat.completions.create(
                    model=model_name,
                    messages=[
                        {"role": "user", "content": prompt}
                    ],
                    temperature=0.8,
                    max_tokens=8192,
                    timeout=TIMEOUT_SECONDS
                )
                output_text = response.choices[0].message.content
            elif content_type == "image":
                # 图文混合调用
                if image_url is None:
                    raise ValueError("image模式需要提供image_url参数")
                
                messages = [
                    {
                        "role": "user",
                        "content": [
                            {"type": "image_url", "image_url": {"url": image_url}},
                            {"type": "text", "text": prompt}
                        ]
                    }
                ]
                response = client.chat.completions.create(
                    model=model_name,
                    messages=messages,
                    temperature=0.8,
                    max_tokens=8192,
                    timeout=TIMEOUT_SECONDS
                )
                output_text = response.choices[0].message.content
            else:
                raise ValueError(f"不支持的内容类型: {content_type}")
        
        elif api_mode == "idealab":
            # 使用idealab模式（使用直接HTTP请求方式，参考qwen_generate_data_forwy.py）
            if api_key is None:
                raise ValueError("idealab模式需要提供api_key参数")
            
            url = 'https://idealab.alibaba-inc.com/api/openai/v1/chat/completions'
            headers = {
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {api_key}'
            }
            
            if content_type == "text":
                # 纯文本调用
                messages = [
                    {
                        "role": "system",
                        "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."
                    },
                    {
                        "role": "user", 
                        "content": prompt
                    }
                ]
            elif content_type == "image":
                # 图文混合调用
                if image_url is None:
                    raise ValueError("image模式需要提供image_url参数")
                
                messages = [
                    {
                        "role": "system",
                        "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."
                    },
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "image_url",
                                "image_url": {
                                    "url": image_url
                                }
                            },
                            {
                                "type": "text",
                                "text": prompt
                            }
                        ]
                    }
                ]
            else:
                raise ValueError(f"不支持的内容类型: {content_type}")
            
            config = {
                "model": model_name,
                "messages": messages,
                "stream": False,
                "max_tokens": 8192,
                "temperature": 0.8,
            }
            
            response = requests.post(url, headers=headers, json=config, timeout=TIMEOUT_SECONDS)
            
            if response.status_code == 200:
                result_json = response.json()
                output_text = result_json['choices'][0]['message']['content']
            else:
                raise Exception(f"HTTP请求失败: {response.status_code}, {response.text}")
        
        else:
            raise ValueError(f"不支持的API模式: {api_mode}")
        
        return output_text
    
    except Exception as e:
        print(f"API调用失败 (mode: {api_mode}, type: {content_type}): {type(e).__name__}: {str(e)}")
        raise e

def is_image_content(sample):
    """
    判断样本是否为图片内容
    现在通过检查是否有oss_url字段来判断
    """
    if isinstance(sample, dict):
        return "oss_url" in sample and sample["oss_url"] is not None and sample["oss_url"].strip() != ""
    else:
        # 兼容旧的调用方式，检查内容是否为图片URL
        return sample.startswith("http") or sample.lower().endswith((".jpg", ".jpeg", ".png"))

def build_full_prompt(question, text, template=None, mode="baseline"):
    # 从 "# 输出格式" 开始截断 question
    if "# 输出格式" in question:
        question_truncated = question.split("# 输出格式")[0].strip()
    else:
        question_truncated = question.strip()
    
    if mode == "baseline":
        full_prompt = f"""{question}"""
    elif mode == "template":
        if not template:
            raise ValueError("使用 template 模式时必须提供 template 内容")

        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

# 推理指导
你必须严格按照以下推理模板进行思考，但请勿直接复述模板内容。模板用于帮助你进行系统性分析，你是否合理使用模板将作为评分依据。

## 推理模板
{template.strip() if template else ""}

# 输出格式要求
请按照以下格式输出：
1. 首先严格按照模板进行分析和推理过程，不要遗漏任何步骤
2. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}
3. box内只能包含答案选项，不允许有其他任何文字

## 示例
分析：...
\\box{{A}}
"""
    elif mode == "cot":
        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

让我们一步步思考...

# 输出格式要求
请按照以下格式输出：
1. 首先进行分析和推理过程
2. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}
3. box内只能包含答案选项，不允许有其他任何文字

## 示例
让我们一步步思考...
\\box{{A}}
"""
    elif mode == "template_miss_keywords":
        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

让我们一步步思考...

# 输出格式要求
请按照以下格式输出：
1. 首先严格按照模板进行分析和推理过程，每一个步骤推理结束后需要声明“该步骤需要填充的具体信息为：xxx”，不要遗漏任何步骤
2. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}
3. box内只能包含答案选项，不允许有其他任何文字

## 示例
让我们一步步思考...
\\box{{...}}
"""
    elif mode == "template_meta":
        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

# 推理模板
{template.strip() if template else ""}

# 推理流程

## 第一部分：关键点预分析
1. 快速扫描：阅读用户提供的文本。
2. 提取核心实体：识别出文本中提到的具体疾病/状况、核心症状、以及提及的产品/疗法等。
3. 结合模板进行多层级、结构化的深度分析

## 第二部分：综合研判与最终结论
现在，请**综合你在“第一部分”的所有初步分析**，并**重新、完整地审视原始文本**，进行最终的判断。在你的结论中，必须：
1.  明确给出一个或多个最终的管控类型（A-Z）。
2.  详细阐述你的推理过程，说明你是如何结合上述关键点，**系统性地整合**以上三个层级的所有分析结果，并进行最终的逻辑推理和规则映射得出这个结论的。如果存在多个判断，请分别说明理由。

# 输出格式要求
1. 第一部分：关键点预分析，逐条作答。
2. 第二部分：综合研判与最终结论，详细说明推理过程。
3. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}，box内只能包含答案选项，不允许有其他任何文字。

## 示例
分析：...
\\box{{...}}
"""
    elif mode == "template_meta_review":
        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

# 推理模板
{template.strip() if template else ""}

# 推理流程

## 第一部分：关键点预分析
在这一部分，请不要直接给出结论。而是针对模板中的 [占位符]（代表问题的关键、复杂、易错的方面）进行分析
1. 快速扫描：阅读用户提供的文本。
2. 提取核心实体：识别出文本中提到的具体疾病/状况、核心症状、以及提及的产品/疗法等。
3. 找到我模板中的 [占位符]。
4. 对[占位符]进行初步分析和判断。

## 第二部分：综合研判与最终结论
现在，请**综合你在“第一部分”的所有初步分析**，并**重新、完整地审视原始文本**，进行最终的判断。在你的结论中，必须：
1.  明确给出一个或多个最终的管控类型（A-Z）。
2.  详细阐述你的推理过程，说明你是如何结合上述关键点（例如，核心宣称与关键实体的组合关系、强信号词的影响、以及是否应用“注意事项”规则）得出这个结论的。如果存在多个判断，请分别说明理由。

# 输出格式要求
1. 第一部分：关键点预分析，逐条作答。
2. 第二部分：综合研判与最终结论，详细说明推理过程。
3. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}，box内只能包含答案选项，不允许有其他任何文字。

## 示例
分析：...
\\box{{A}}
"""
    elif mode == "template_meta_review_v1":
        full_prompt = f"""{question_truncated}

# 给定信息
{text.strip()}

# 推理模板
{template.strip() if template else ""}

# 推理流程

## 第一部分：关键点预分析
在这一部分，请不要直接给出结论。而是针对模板中的 [占位符]（代表问题的关键、复杂、易错的方面）进行分析
1. 快速扫描：阅读用户提供的文本。
2. 提取核心实体：识别出文本中提到的具体疾病/状况、核心症状、以及提及的产品/疗法等。
3. 找到我模板中的 [占位符]。
4. 对[占位符]进行初步分析和判断。

## 第二部分：综合研判与最终结论
现在，请**综合你在“第一部分”的所有初步分析**，并**重新、完整地审视原始文本**，进行最终的判断。在你的结论中，必须：
1.  明确给出一个或多个最终的管控类型（A-Z）。
2.  详细阐述你的推理过程，说明你是如何结合上述关键点（例如，核心宣称与关键实体的组合关系、强信号词的影响、以及是否应用“注意事项”规则）得出这个结论的。如果存在多个判断，请分别说明理由。

# 输出要求
你的唯一任务是判断文本是否触发了管控规则。请像一个精准的规则引擎一样工作。任何不直接触发这些规则的描述都应被视为无关信息，除非它们本身构成了夸大承诺。请不要被这些无关信息干扰你的判断，聚焦于寻找并匹配违规信号。
1. 第一部分：关键点预分析，逐条作答。
2. 第二部分：综合研判与最终结论，详细说明推理过程。
3. 最后用\\box{{}}输出最终答案，格式为\\box{{A~Z中的一个或多个选项}}，box内只能包含答案选项，不允许有其他任何文字。

## 示例
分析：...
\\box{{A}}
"""
    elif mode == "template_meta_review_v2":
        full_prompt = f"""你是一个严谨的分析引擎。你的唯一任务是严格遵循下面提供的`推理模板`中的每一个步骤，对`给定信息`进行分析，并得出最终结论。模板中的每一步都是你必须执行的指令。
{question_truncated}

# 给定信息
{text.strip()}

# 推理模板
{template.strip() if template else ""}

# 推理流程

## 第一部分：整体分析
首先忽略细节，通读给定信息，形成一个初步的、整体的判断。

## 第二部分：搜集证据
在这一部分，请不要直接给出结论。而是针对模板中的 [占位符]（代表问题的关键、复杂、易错的方面）进行分析，来尝试证明你的整体分析中的结论。
1. 快速扫描：阅读用户提供的文本。
2. 提取核心实体：识别出文本中提到的具体疾病/状况、核心症状、以及提及的产品/疗法等。
3. 找到模板中的 [占位符]。
4. 对[占位符]进行初步分析和判断。

## 第三部分：综合研判与最终结论
综合以上`模板化推理过程`中每一步的分析结果，进行最终的判断，决定是否符合整体判断，还是根据第二部分的证据发现了新的结论。在你的结论中，必须：
1.  明确给出一个或多个最终的管控类型（A-Z）。
2.  详细阐述你的推理过程，说明你是如何整合各步骤的发现，最终得出这个结论的。

# 输出要求
你的唯一任务是判断文本是否触发了管控规则。请像一个精准的规则引擎一样工作，聚焦于寻找并匹配违规信号。
1. **第一部分：模板化推理过程**，严格按照模板步骤，逐条展示分析过程和发现。
2. **第二部分：综合研判与最终结论**，基于第一部分的分析进行汇总和阐述。
3. 最后用`\box{{}}`输出最终答案，格式为`\box{{A~Z中的一个或多个选项}}`，box内只能包含答案选项，不允许有其他任何文字。

## 示例
分析：...
\\box{{A}}"""

    elif mode == "template_meta_review_v2_ablation":
        full_prompt = f"""你是一个严谨的分析引擎。你的唯一任务是严格遵循下面提供的`推理模板`中的每一个步骤，对`给定信息`进行分析，并得出最终结论。模板中的每一步都是你必须执行的指令。
{question_truncated}

# 给定信息
{text.strip()}

# 推理模板
{template.strip() if template else ""}

# 推理流程

## 第一部分：整体分析
首先忽略细节，通读给定信息，形成一个初步的、整体的判断。

## 第二部分：搜集证据
在这一部分，请不要直接给出结论。而是针对模板中的 [占位符]（代表问题的关键、复杂、易错的方面）进行分析，来尝试证明你的整体分析中的结论。
1. 快速扫描：阅读用户提供的文本。
2. 提取核心实体：识别出文本中提到的具体疾病/状况、核心症状、以及提及的产品/疗法等。
3. 找到模板中的 [占位符]。
4. 对[占位符]进行初步分析和判断。

# 输出要求
你的唯一任务是判断文本是否触发了管控规则。请像一个精准的规则引擎一样工作，聚焦于寻找并匹配违规信号。
1. **第一部分：模板化推理过程**，严格按照模板步骤，逐条展示分析过程和发现。
2. 最后用`\box{{}}`输出最终答案，格式为`\box{{A~Z中的一个或多个选项}}`，box内只能包含答案选项，不允许有其他任何文字。

## 示例
分析：...
\\box{{A}}"""

    elif mode == "template_meta_review_image_v1":
        full_prompt = f"""你是一个严谨细致的**多模态内容分析师**。你的任务是严格遵循下面提供的`推理模板`中的每一个步骤，对`给定图片内容`（包括其视觉元素和提取出的文本）进行综合分析，并得出最终结论。
{question_truncated}

# 推理模板
{template.strip() if template else ""}

# 输出要求
## 1. 分析过程
严格按照`# 分析步骤`，逐条展示你的分析过程和发现。

## 2. 最终结论
综合你的分析过程，给出最终结论和详细的推理理由。

## 3. 最终答案
用`\\box{{}}`输出最终答案，格式为`\\box{{A~Z中的一个或多个选项}}`，box内只能包含答案选项，不允许有其他任何文字。"""

    else:
        raise ValueError("mode 参数只能是 'baseline' 或 'template' 或 'cot' ")

    return full_prompt

def load_all_templates_from_json(selected_content_type, template_json_path):
    """
    从指定的json文件(template_dict_20250626_180036.json)中，选出所有selected_content_type的模板（origin和generated）。
    返回格式: {template_name: template_content, ...}
    """
    with open(template_json_path, 'r', encoding='utf-8') as f:
        template_dict = json.load(f)
    all_templates = {}
    # origin部分
    origin = template_dict.get('origin', {})
    for v_key, v_val in origin.items():
        if selected_content_type in v_val:
            for t_name, t_content in v_val[selected_content_type].items():
                if t_name not in all_templates:
                    all_templates[t_name] = t_content
    # generated部分
    generated = template_dict.get('generated', {})
    if selected_content_type in generated:
        for t_name, t_content in generated[selected_content_type].items():
            if t_name not in all_templates:
                all_templates[t_name] = t_content
    return all_templates

def select_templates_by_config(templates_dict, selection_mode="all", template_count=None, selection_seed=42, start_index=None, end_index=None):
    """
    根据配置参数选择模板
    
    Args:
        templates_dict: 模板字典 {template_name: template_content, ...}
        selection_mode: 选择模式 ("all", "sequential", "random", "index")
        template_count: 选择数量，None表示全部
        selection_seed: 随机选择的种子
        start_index: 起始索引（仅在index模式下有效）
        end_index: 结束索引（仅在index模式下有效）
    
    Returns:
        dict: 选择后的模板字典
    """
    if not templates_dict:
        return {}
    
    template_items = list(templates_dict.items())
    
    if selection_mode == "all":
        # 使用所有模板
        selected_templates = dict(template_items)
    elif selection_mode == "sequential":
        # 按顺序选择前N个模板
        if template_count is None:
            selected_templates = dict(template_items)
        else:
            count = min(template_count, len(template_items))
            selected_templates = dict(template_items[:count])
    elif selection_mode == "random":
        # 随机选择N个模板
        if template_count is None:
            selected_templates = dict(template_items)
        else:
            count = min(template_count, len(template_items))
            random.seed(selection_seed)
            selected_items = random.sample(template_items, count)
            selected_templates = dict(selected_items)
    elif selection_mode == "index":
        # 按索引范围选择模板
        start = start_index if start_index is not None else 0
        end = end_index if end_index is not None else len(template_items)
        # 确保索引范围有效
        start = max(0, min(start, len(template_items)))
        end = max(start, min(end, len(template_items)))
        selected_templates = dict(template_items[start:end])
    else:
        raise ValueError(f"不支持的模板选择模式: {selection_mode}")
    
    if selection_mode == "index":
        print(f"模板选择结果: 模式={selection_mode}, 总数={len(templates_dict)}, 选择数={len(selected_templates)}, 索引范围=[{start_index}, {end_index})")
    else:
        print(f"模板选择结果: 模式={selection_mode}, 总数={len(templates_dict)}, 选择数={len(selected_templates)}")
    # print(f"选择的模板: {list(selected_templates.keys())}")
    
    return selected_templates

# ========= 评估函数 =========
# 通用线程 worker
def run_samples_worker(
    data_chunk, save_file_name, template_name, content_type, model_name, client, finished_pairs, missed_ids, missed_ids_lock, content_mode=None, resume_results=None, templates_to_use=None, api_mode="bailian", template_prompt_mode="template_meta_review", templates_text=None, templates_image=None, api_key=None
):
    handle_results = []
    is_first_sample = True  # 标志位，用于控制只在第一条样本输出prompt
    for sample in tqdm(data_chunk, desc=f"[Thread-{template_name}] processing"):
        sample_id = sample["id"]
        pair_key = (sample_id, template_name)
        if pair_key in finished_pairs:
            continue
        # 根据content_mode获取相应的内容
        if content_mode == "image":
            # 图片模式：从oss_url获取图片URL，从content获取文本用于构建prompt
            image_url = sample.get("oss_url", "")
            text = sample["content"]  # 用于构建prompt的文本内容
        else:
            # 文本模式：从content获取文本
            text = sample["content"]
            image_url = None
        
        question = sample["question"]
        gt = eval(sample["选项"])
        if template_name == "baseline":
            full_prompt = build_full_prompt(question, text, mode="baseline")
        elif template_name == "CoT":
            full_prompt = build_full_prompt(question, text, mode="cot")
        else:
            if content_type is None:
                raise ValueError("content_type 不能为空，必须指定用于选择模板的类型")
            if templates_to_use is not None:
                template_content = templates_to_use[content_type][template_name]
            elif content_mode == "image":
                if templates_image is None:
                    raise ValueError("templates_image 不能为空")
                template_content = templates_image[content_type][template_name]
            else:
                if templates_text is None:
                    raise ValueError("templates_text 不能为空")
                template_content = templates_text[content_type][template_name]
            full_prompt = build_full_prompt(question, text, template=template_content, mode=template_prompt_mode)
        retry_count = 0
        prediction = None
        output_text = None
        
        # 只在第一条样本时输出完整的prompt
        # if is_first_sample:
        #     print(f"\n{'='*80}")
        #     print(f"[Sample ID: {sample_id}] [Template: {template_name}]")
        #     print(f"{'='*80}")
        #     print("COMPLETE PROMPT:")
        #     print(f"{'-'*80}")
        #     print(full_prompt)
        #     print(f"{'-'*80}")
        #     print("END OF PROMPT\n")
        #     is_first_sample = False  # 设置标志位，后续样本不再输出
        
        while retry_count < RETRY_TIMES:
            try:
                start_time = time.time()
                
                # 只在重试时输出简短信息
                # if retry_count > 0:
                #     print(f"[Sample ID: {sample_id}] Retry attempt {retry_count + 1}")
                
                if content_mode == "image":
                    output_text = call_qwen_unified(
                        prompt=full_prompt, 
                        content_type="image", 
                        image_url=image_url, 
                        client=client, 
                        model_name=model_name, 
                        api_mode=api_mode,
                        api_key=api_key
                    )
                else:
                    output_text = call_qwen_unified(
                        prompt=full_prompt, 
                        content_type="text", 
                        client=client, 
                        model_name=model_name, 
                        api_mode=api_mode,
                        api_key=api_key
                    )
                
                # 检查是否超时
                elapsed_time = time.time() - start_time
                if elapsed_time > TIMEOUT_SECONDS:
                    print(f"样本 {sample_id} 处理超时 ({elapsed_time:.1f}s)，重试...")
                    retry_count += 1
                    continue
                
                # 根据模板类型选择不同的答案提取方法
                if template_name == "baseline":
                    # baseline模式使用JSON结论提取
                    prediction = extract_json_conclusion(output_text)
                else:
                    # 其他模板模式使用box答案提取
                    prediction = extract_box_answer(output_text)
                
                if prediction:
                    # 成功提取到答案，包括"no_answer"这种兜底情况
                    break
                else:
                    # 如果输出格式不正确，输出调试信息并重试
                    # print(f"样本 {sample_id} 输出格式不正确，重试...")
                    # print(f"[DEBUG] 模板: {template_name}, 模型输出长度: {len(output_text) if output_text else 0}")
                    # if output_text:
                    #     # 输出前500字符用于调试
                    #     print(f"[DEBUG] 输出内容预览: {output_text[:500]}...")
                    #     if "\\box" in output_text:
                    #         print(f"[DEBUG] 发现box关键词，但提取失败")
                    #     if "结论" in output_text:
                    #         print(f"[DEBUG] 发现'结论'关键词，但提取失败")
                    retry_count += 1
                    if retry_count >= RETRY_TIMES:
                        # print(f"样本 {sample_id} 输出格式问题，使用宽松提取")
                        # 宽松提取：尝试提取任何包含A-Z字母的内容
                        loose_prediction = extract_loose_answer(output_text)
                        if loose_prediction:
                            prediction = loose_prediction
                            print(f"[DEBUG] 宽松提取成功: {prediction}")
                        else:
                            prediction = ["[ERROR]"]
                            print(f"[DEBUG] 宽松提取也失败，标记为错误")
                        break
                        
            except Exception as e:
                retry_count += 1
                print(f"样本 {sample_id} 第 {retry_count} 次重试时出错: {type(e).__name__}: {str(e)}")
                if retry_count == 1:  # 第一次出错时就输出详细信息
                    print(f"样本 {sample_id} output_text: {output_text if 'output_text' in locals() else '无'}")
                if retry_count >= RETRY_TIMES:
                    print(f"[FATAL] 样本 {sample_id} {RETRY_TIMES} 次都失败，最后一次异常: {str(e) if 'e' in locals() else '未知'}")
                    with missed_ids_lock:
                        missed_ids.add(sample_id)
                    # 添加短暂延迟避免频繁重试
                    time.sleep(RETRY_DELAY)
        
        if retry_count < RETRY_TIMES:
            result_entry = {
                "id": sample_id,
                "content_type": content_type,
                "template_name": template_name,
                "input": text,
                "gt": gt,
                "pred": prediction,
                "raw_output": output_text
            }
            handle_results.append(result_entry)
            with missed_ids_lock:
                missed_ids.discard(sample_id)
            finished_pairs.add(pair_key)
            if resume_results is not None:
                resume_results[pair_key] = result_entry
    
    # 在函数结束时写入一次完整结果
    with open(save_file_name, "w", encoding="utf-8") as f:
        json.dump(handle_results, f, ensure_ascii=False, indent=2)
    # print(f"[INFO] 轮次 {round_idx} 结束，剩余未处理样本: {missed_ids}")
    return handle_results

# 主函数，多线程版
def run_template_evaluation_multithread(
    all_samples, output_path, client, model_name, selected_content_type=None,
    content_mode="all", sample_size=None, seed=42, selected_ids=None, finished_pairs=set(), resume_results=None,
    templates=None, api_mode="bailian", template_prompt_mode="template_meta_review", templates_text=None, templates_image=None, api_key=None
):
    global missed_ids, thread_status
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    samples = all_samples
    if selected_content_type is None:
        raise ValueError("selected_content_type 不能为空，必须指定用于选择模板的类型")
    type_samples = [s for s in samples if s["content_type"] == selected_content_type]
    if selected_ids is not None:
        type_samples = [s for s in type_samples if s["id"] in selected_ids]
    if content_mode == "text":
        type_samples = [s for s in type_samples if not is_image_content(s)]
    elif content_mode == "image":
        type_samples = [s for s in type_samples if is_image_content(s)]
    # 模板选择逻辑重构
    if templates is not None:
        templates_to_use = templates
    elif content_mode == "text":
        if templates_text is None:
            raise ValueError("templates_text 不能为空")
        templates_to_use = templates_text
    elif content_mode == "image":
        if templates_image is None:
            raise ValueError("templates_image 不能为空")
        templates_to_use = templates_image
    else:
        if templates_text is None:
            raise ValueError("templates_text 不能为空")
        templates_to_use = templates_text
    all_sample_ids = {s["id"] for s in type_samples}
    max_rounds = 5
    
    # 初始化输出文件（清空或创建）
    with open(output_path, "w", encoding="utf-8") as f:
        pass  # 创建空文件
    
    for template_name, template in templates_to_use[selected_content_type].items():
        print(f"\n==============================\n[TESTING TEMPLATE] 当前正在测试模板: {template_name}\n==============================")
        print(f"  ➜ Running {template_name} with multithreading")
        missed_ids = all_sample_ids.copy()
        round_idx = 0
        template_results = []  # 收集当前template的所有结果
        
        while missed_ids and round_idx < max_rounds:
            print(f"    ➜ Round {round_idx+1}, {len(missed_ids)} samples to process...")
            if round_idx == 0:
                samples_to_run = [s for s in type_samples if s["id"] in missed_ids]
            else:
                samples_to_run = [s for s in samples if s["id"] in missed_ids and s["content_type"] == selected_content_type]
                if content_mode == "text":
                    samples_to_run = [s for s in samples_to_run if not is_image_content(s)]
                elif content_mode == "image":
                    samples_to_run = [s for s in samples_to_run if is_image_content(s)]
            chunks = np.array_split(samples_to_run, THREAD_NUMS)
            threads = []
            thread_results_list = [None] * THREAD_NUMS  # 新增：收集每个线程结果
            thread_start_times = [0.0] * THREAD_NUMS  # 记录线程开始时间，初始化为0.0
            
            def thread_func_wrapper(thread_id, *args, **kwargs):
                with thread_status_lock:
                    thread_status[thread_id] = {"start_time": time.time(), "status": "running"}
                try:
                    thread_results_list[thread_id] = run_samples_worker(*args, **kwargs)
                    with thread_status_lock:
                        thread_status[thread_id]["status"] = "completed"
                except Exception as e:
                    print(f"线程 {thread_id} 异常: {e}")
                    with thread_status_lock:
                        thread_status[thread_id]["status"] = "failed"
            
            # 启动所有线程
            for thread_id in range(THREAD_NUMS):
                temp_file_name = os.path.join(TEMP_OUTPUT_DIR, f"thread_{selected_content_type}_{template_name}_{model_name}_round{round_idx}_{thread_id}.json")
                thread = threading.Thread(
                    target=thread_func_wrapper,
                    args=(thread_id, chunks[thread_id], temp_file_name, template_name, selected_content_type, model_name, client, finished_pairs, missed_ids, missed_ids_lock, content_mode, resume_results, templates_to_use, api_mode, template_prompt_mode, templates_text, templates_image, api_key)
                )
                threads.append(thread)
                thread.start()
                thread_start_times[thread_id] = time.time()
            
            # 监控线程状态
            while any(thread.is_alive() for thread in threads):
                current_time = time.time()
                with thread_status_lock:
                    for thread_id, thread in enumerate(threads):
                        if thread.is_alive():
                            elapsed = current_time - thread_start_times[thread_id]
                            if elapsed > THREAD_TIMEOUT_SECONDS:
                                print(f"线程 {thread_id} 超时 ({elapsed:.1f}s)，强制终止...")
                                # 这里可以添加强制终止逻辑，但Python线程无法强制终止
                                # 只能等待或重启整个轮次
                
                time.sleep(10)  # 每10秒检查一次
            
            # 等待所有线程完成
            for thread in threads:
                thread.join()
            
            # 合并本轮所有线程结果
            for thread_results in thread_results_list:
                if thread_results:
                    template_results.extend(thread_results)
            round_idx += 1
        
        # 每个template完成后立即写入结果，防止中间断掉
        print(f"  ✅ Template {template_name} completed, writing {len(template_results)} results to file...")
        template_results_dict = {}
        for entry in template_results:
            key = (entry["id"], entry["template_name"])
            template_results_dict[key] = entry
        
        # 追加写入当前template的结果
        with open(output_path, "a", encoding="utf-8") as f:
            for result_entry in template_results_dict.values():
                f.write(json.dumps(result_entry, ensure_ascii=False) + "\n")
    
    print(f"🎉 All templates completed! Results saved to: {output_path}")
    # 记录最终未处理的样本
    if missed_ids:
        print(f"💡 Some samples were not processed after {max_rounds} rounds: {missed_ids}")
        missed_file_path = os.path.join(TEMP_OUTPUT_DIR, f"missed_samples_{selected_content_type}_{template_name}_{model_name}.json")
        with open(missed_file_path, "w", encoding="utf-8") as f:
            json.dump(list(missed_ids), f, ensure_ascii=False, indent=2)
    else:
        print("✅ All samples have been processed successfully.")

def run_template_evaluation_singlethread(
    all_samples, output_path, client, model_name, selected_content_type=None,
    content_mode="all", sample_size=None, seed=42, selected_ids=None, finished_pairs=set(), resume_results=None,
    templates=None, api_mode="bailian", template_prompt_mode="template_meta_review", templates_text=None, templates_image=None, api_key=None
):
    """单线程版本的模板评估函数，避免多线程卡住问题"""
    global missed_ids
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    samples = all_samples
    if selected_content_type is None:
        raise ValueError("selected_content_type 不能为空，必须指定用于选择模板的类型")
    type_samples = [s for s in samples if s["content_type"] == selected_content_type]
    if selected_ids is not None:
        type_samples = [s for s in type_samples if s["id"] in selected_ids]
    if content_mode == "text":
        type_samples = [s for s in type_samples if not is_image_content(s)]
    elif content_mode == "image":
        type_samples = [s for s in type_samples if is_image_content(s)]
    
    # 模板选择逻辑
    if templates is not None:
        templates_to_use = templates
    elif content_mode == "text":
        if templates_text is None:
            raise ValueError("templates_text 不能为空")
        templates_to_use = templates_text
    elif content_mode == "image":
        if templates_image is None:
            raise ValueError("templates_image 不能为空")
        templates_to_use = templates_image
    else:
        if templates_text is None:
            raise ValueError("templates_text 不能为空")
        templates_to_use = templates_text
    
    all_sample_ids = {s["id"] for s in type_samples}
    max_rounds = 5
    
    # 初始化输出文件（清空或创建）
    with open(output_path, "w", encoding="utf-8") as f:
        pass  # 创建空文件
    
    for template_name, template in templates_to_use[selected_content_type].items():
        print(f"\n==============================\n[TESTING TEMPLATE] 当前正在测试模板: {template_name}\n==============================")
        print(f"  ➜ Running {template_name} with single threading")
        missed_ids = all_sample_ids.copy()
        round_idx = 0
        template_results = []  # 收集当前template的所有结果
        
        while missed_ids and round_idx < max_rounds:
            print(f"    ➜ Round {round_idx+1}, {len(missed_ids)} samples to process...")
            if round_idx == 0:
                samples_to_run = [s for s in type_samples if s["id"] in missed_ids]
            else:
                samples_to_run = [s for s in samples if s["id"] in missed_ids and s["content_type"] == selected_content_type]
                if content_mode == "text":
                    samples_to_run = [s for s in samples_to_run if not is_image_content(s)]
                elif content_mode == "image":
                    samples_to_run = [s for s in samples_to_run if is_image_content(s)]
            
            # 单线程处理所有样本
            temp_file_name = os.path.join(TEMP_OUTPUT_DIR, f"singlethread_{selected_content_type}_{template_name}_{model_name}_round{round_idx}.json")
            thread_results = run_samples_worker(
                samples_to_run, temp_file_name, template_name, selected_content_type, 
                model_name, client, finished_pairs, missed_ids, missed_ids_lock, 
                content_mode, resume_results, templates_to_use, api_mode, template_prompt_mode, templates_text, templates_image, api_key
            )
            
            if thread_results:
                template_results.extend(thread_results)
            round_idx += 1
        
        # 每个template完成后立即写入结果，防止中间断掉
        print(f"  ✅ Template {template_name} completed, writing {len(template_results)} results to file...")
        template_results_dict = {}
        for entry in template_results:
            key = (entry["id"], entry["template_name"])
            template_results_dict[key] = entry
        
        # 追加写入当前template的结果
        with open(output_path, "a", encoding="utf-8") as f:
            for result_entry in template_results_dict.values():
                f.write(json.dumps(result_entry, ensure_ascii=False) + "\n")
    
    print(f"🎉 All templates completed! Results saved to: {output_path}")
    
    # 记录最终未处理的样本
    if missed_ids:
        print(f"💡 Some samples were not processed after {max_rounds} rounds: {missed_ids}")
        missed_file_path = os.path.join(TEMP_OUTPUT_DIR, f"missed_samples_{selected_content_type}_{template_name}_{model_name}.json")
        with open(missed_file_path, "w", encoding="utf-8") as f:
            json.dump(list(missed_ids), f, ensure_ascii=False, indent=2)
    else:
        print("✅ All samples have been processed successfully.")

# ========= 结果分析 =========
def normalize_conclusion(conclusion):
    """
    规范化"结论"字段，提取出单独的字母并转为小写形式。
    新版本：支持从box中提取的答案格式，以及兼容旧的JSON格式
    """
    if isinstance(conclusion, list):
        # 递归处理每个元素
        results = []
        for item in conclusion:
            results.extend(normalize_conclusion(item))
        return results
    if not isinstance(conclusion, str):
        return []
    
    # 新格式：从 box 中提取的答案已经是很干净的格式，直接处理即可
    conclusion = conclusion.strip()
    
    # 先尝试匹配形如 "S.描述" 或 "A.描述"，优先取点前字母
    m = re.match(r"([A-Za-z])\\.", conclusion)
    if m:
        return [m.group(1).lower()]
    
    # 替换可能的连接符（如 ',' 或 '～'），并将字母转换为小写
    conclusion = conclusion.replace("～", ",").replace("，", ",")
    
    # 使用正则提取字母（A-Z）
    conclusions = re.findall(r"[A-Za-z]", conclusion)
    
    # 过滤出有效的选项（A-Z）
    valid_conclusions = []
    for letter in conclusions:
        letter_upper = letter.upper()
        if 'A' <= letter_upper <= 'Z':
            valid_conclusions.append(letter_upper.lower())
    
    return valid_conclusions

# 结果分析封装函数
def analyze_results_and_generate_log(
    results_json_path, output_log_path, templates, selected_content_type, model_name_use=None, align_sample_ids=True
):
    all_results = []
    with open(results_json_path, "r", encoding="utf-8") as f:
        for line in f:
            if line.strip():
                all_results.append(json.loads(line.strip()))

    metrics = defaultdict(lambda: {"exact_correct": 0, "overlap_correct": 0, "total": 0})

    all_sample_ids = defaultdict(set)
    for template_name in templates[selected_content_type].keys():
        for entry in all_results:
            if entry["template_name"] == template_name:
                all_sample_ids[template_name].add(entry["id"])

    if align_sample_ids:
        # 对齐模式：只统计所有模板都覆盖的样本
        sample_id_sets = list(all_sample_ids.values())
        if len(sample_id_sets) > 0:
            common_ids = set.intersection(*sample_id_sets)
        else:
            common_ids = set()
        for entry in all_results:
            if entry["id"] not in common_ids:
                continue
            gt = set([x.lower() for x in entry["gt"]])
            pred_norm = normalize_conclusion(entry["pred"])
            pred_set = set(pred_norm)
            if "[ERROR]" in pred_set:
                continue
            key = f"{entry['content_type']}_{entry['template_name']}"
            metrics[key]["total"] += 1
            if pred_set == gt:
                metrics[key]["exact_correct"] += 1
            if len(pred_set & gt) > 0:
                metrics[key]["overlap_correct"] += 1
    else:
        # 各自独立模式：每个模板各自统计
        for template_name in templates[selected_content_type].keys():
            template_ids = all_sample_ids[template_name]
            for entry in all_results:
                if entry["template_name"] != template_name:
                    continue
                if entry["id"] not in template_ids:
                    continue
                gt = set([x.lower() for x in entry["gt"]])
                pred_norm = normalize_conclusion(entry["pred"])
                pred_set = set(pred_norm)
                if "[ERROR]" in pred_set:
                    continue
                key = f"{entry['content_type']}_{entry['template_name']}"
                metrics[key]["total"] += 1
                if pred_set == gt:
                    metrics[key]["exact_correct"] += 1
                if len(pred_set & gt) > 0:
                    metrics[key]["overlap_correct"] += 1

    with open(output_log_path, "w", encoding="utf-8") as log_f:
        now_str = datetime.datetime.now().strftime("%Y%m%d_%H%M")
        log_f.write(f"📄 模板评估日志\n")
        log_f.write(f"时间：{now_str}\n")
        log_f.write(f"内容类型 (content_type)：{selected_content_type}\n")
        log_f.write(f"模型名称 (model_name_use)：{model_name_use}\n")
        log_f.write(f"使用模板：{list(templates[selected_content_type].keys())}\n\n")
        log_f.write(f"对齐模式：{align_sample_ids}\n\n")
        log_f.write("📊 模板评估结果 (准确率):\n")
        for key, val in metrics.items():
            exact_acc = val["exact_correct"] / val["total"] if val["total"] else 0
            overlap_acc = val["overlap_correct"] / val["total"] if val["total"] else 0
            result_line = f"{key:<40}: Exact Acc = {exact_acc:.2%} ({val['exact_correct']}/{val['total']}), Overlap Acc = {overlap_acc:.2%} ({val['overlap_correct']}/{val['total']})\n"
            log_f.write(result_line)
        log_f.write("\n模板详情：\n")
        for template_name, template_content in templates[selected_content_type].items():
            log_f.write(f"\n{template_name}:\n{template_content.strip()}\n")

def run_all_evaluations(
    output_json_path,
    output_log_path,
    output_log_new_path,
    selected_content_type,
    model_name_use,
    content_mode,
    file_suffix
):
    # 原有模板分析
    if content_mode == "image":
        templates_for_analysis = TEMPLATES_IMAGE
    else:
        templates_for_analysis = TEMPLATES_TEXT
    analyze_results_and_generate_log(
        results_json_path=output_json_path,
        output_log_path=output_log_new_path,
        templates=templates_for_analysis,
        selected_content_type=selected_content_type,
        model_name_use=model_name_use,
        align_sample_ids=False
    )
    analyze_results_and_generate_log(
        results_json_path=output_json_path,
        output_log_path=output_log_path,
        templates=templates_for_analysis,
        selected_content_type=selected_content_type,
        model_name_use=model_name_use,
        align_sample_ids=True
    )
    # json模板分析
    template_json_path = os.path.join(PROJECT_ROOT, "code", "template_dict_20250626_180036.json")
    if os.path.exists(template_json_path):
        all_templates = load_all_templates_from_json(selected_content_type, template_json_path)
        templates_for_json_analysis = {selected_content_type: all_templates}
        output_log_json_path = os.path.join(PROJECT_ROOT, "data", "eval", selected_content_type, model_name_use, f"template_{file_suffix}_json_templates.md")
        output_log_json_sorted_path = os.path.join(PROJECT_ROOT, "data", "eval", selected_content_type, model_name_use, f"template_{file_suffix}_json_templates_sorted.md")
        analyze_results_and_generate_log(
            results_json_path=output_json_path,
            output_log_path=output_log_json_path,
            templates=templates_for_json_analysis,
            selected_content_type=selected_content_type,
            model_name_use=model_name_use,
            align_sample_ids=False
        )
        def sort_templates_by_accuracy(log_path, sorted_path):
            acc_lines = []
            with open(log_path, 'r', encoding='utf-8') as f:
                lines = f.readlines()
            for line in lines:
                if 'Exact Acc' in line:
                    acc_lines.append(line)
            acc_lines_sorted = sorted(acc_lines, key=lambda x: float(x.split('=')[1].split('(')[0].strip()), reverse=True)
            with open(sorted_path, 'w', encoding='utf-8') as f:
                f.write(''.join(acc_lines_sorted))
        sort_templates_by_accuracy(output_log_json_path, output_log_json_sorted_path)
    else:
        print(f"跳过JSON模板分析，文件不存在: {template_json_path}")

# ========= 主程序 =========
if __name__ == "__main__":
    # ==========================================================================
    # 配置参数设置 - 在这里修改所有需要调整的参数
    # ==========================================================================
    
    # ========== 模型配置 ==========
    # 文本模型
    # model_name_use = "qwen2.5-7b-instruct"
    model_name_use = "qwen2.5-14b-instruct"
    # model_name_use = "qwen2.5-32b-instruct"
    # model_name_use = "qwen2.5-72b-instruct"
    # model_name_use = "deepseek-r1-distill-llama-8b"
    # model_name_use = "llama-4-scout-17b-16e-instruct"
    # model_name_use = "qwen-max"
    model_name_use = "gpt-41-0414-global"
    # model_name_use = "deepseek-r1"
    
    # 图像模型
    # model_name_use = "qwen2.5-vl-3b-instruct"
    # model_name_use = "qwen2.5-vl-7b-instruct"
    # model_name_use = "qwen2.5-vl-32b-instruct"
    # model_name_use = "qwen2.5-vl-72b-instruct"
    # model_name_use = "qwen-vl-max"
    # model_name_use = "qwen-vl-max-2025-04-08"
    
    # ========== 数据配置 ==========
    # origin_data = "evade_dataset_test_text.json"
    origin_data = [
        "evade_dataset_test_text.json",
        "evade_dataset_train_text.json"
    ]
    # selected_content_type = "fengxiong"  # 可选: "suoyin", "jianfei", "fengxiong", "disease", "tall", "zhuangyang"
    for selected_content_type in ["suoyin", "jianfei", "fengxiong", "disease", "tall", "zhuangyang"]:
        content_mode = "text"             # 可选: "text", "image", "all"
        sample_size = None                 # 采样数量，None表示全量
        seed = 30                         # 随机种子

        # 导入模板字典
        from core.template_dict import TEMPLATES_TEXT as TEMPLATES_TEXT, TEMPLATES_IMAGE_250716_TWO_STAGE as TEMPLATES_IMAGE

        # ========== API配置 ==========
        api_mode = "idealab"              # 可选: "bailian" 或 "idealab"

        # 根据API模式设置对应的API key
        if api_mode == "bailian":
            api_key = "xxx"  
        elif api_mode == "idealab":
            api_key = "xxx"
        else:
            raise ValueError(f"不支持的API模式: {api_mode}")

        # ========== 运行配置 ==========
        # 线程设置
        use_multithreading = True         # 可选: True 或 False
        THREAD_NUMS = 50                   # 线程数（仅在use_multithreading=True时有效），降低并发以减少错误

        # 模板配置
        template_mode = "variable"        # 可选: "json" 或 "variable"

        # Template推理Prompt模式配置
        # 可选的模式有：
        # - "baseline": 基础模式，无模板
        # - "template_miss_keywords": 模板缺失关键词模式
        # - "template_meta": 模板元分析模式
        # - "template_meta_review": 模板元分析审查模式
        if content_mode == "image":
            template_prompt_mode = "template_meta_review_v2"
        else:
            template_prompt_mode = "template_meta_review_v2"

        # ========== 模板选择配置 ==========
        # 模板选择模式
        template_selection_mode = "all"   # 可选: "all", "sequential", "random", "index"
                                          # - "all": 使用所有模板
                                          # - "sequential": 按顺序选择前N个模板
                                          # - "random": 随机选择N个模板
                                          # - "index": 按索引范围选择模板
        # 模板选择数量
        template_count = 10                # 选择的模板数量，None表示全部
                                          # 仅在template_selection_mode为"sequential"或"random"时有效
        # 模板选择随机种子（仅在random模式下有效）
        template_selection_seed = 42      # 模板选择的随机种子

        # 模板索引范围配置（仅在index模式下有效）
        template_start_index = 0          # 起始索引（包含）
        template_end_index = 1            # 结束索引（不包含），None表示到末尾

        # 断点续跑配置
        # resume_jsonl_path = "data/eval/suoyin/qwen2.5-7b-instruct/template_suoyin_text_200_seed-30_qwen2.5-7b-instruct_20250714_1643.jsonl" # 如需断点续跑，填写已有的jsonl路径，否则为None
        resume_jsonl_path = None

        # ==========================================================================
        # 以下代码自动运行，一般不需要修改
        # ==========================================================================

        print(f"🚀 开始运行模板评估")
        print(f"   模型: {model_name_use}")
        print(f"   数据类型: {selected_content_type}")
        print(f"   内容模式: {content_mode}")
        print(f"   样本数: {sample_size}")
        print(f"   API模式: {api_mode}")
        print(f"   Template模式: {template_prompt_mode}")
        print(f"   模板选择模式: {template_selection_mode}")
        print(f"   模板选择数量: {template_count}")
        print(f"   多线程: {use_multithreading}")
        print("=" * 60)

        # 初始化客户端
        if api_mode == "bailian":
            client = get_openai_client(api_key)
        else:
            client = None  # idealab模式会在统一函数中自动创建客户端

        now_str = datetime.datetime.now().strftime("%Y%m%d_%H%M")
        file_suffix = f"{selected_content_type}_{content_mode}_{sample_size}_seed-{seed}_{model_name_use}_{now_str}"
        output_json_path = os.path.join(PROJECT_ROOT, "data", "eval", selected_content_type, model_name_use, f"template_{file_suffix}.jsonl")
        output_log_path = os.path.join(PROJECT_ROOT, "data", "eval", selected_content_type, model_name_use, f"template_{file_suffix}.md")

        #########################################################################
        # ====== 统一抽样 ======
        def load_and_merge_json_files(origin_data, base_dir):
            if isinstance(origin_data, str):
                origin_data = [origin_data]
            all_samples = []
            for fname in origin_data:
                dataset_path = os.path.join(base_dir, fname)
                with open(dataset_path, "r", encoding="utf-8") as f:
                    samples = json.load(f)
                    all_samples.extend(samples)
            return all_samples

        dataset_base_dir = os.path.join(PROJECT_ROOT, "data", "dataset", "EVADE")
        all_samples = load_and_merge_json_files(origin_data, dataset_base_dir)
        type_samples = [s for s in all_samples if s["content_type"] == selected_content_type]
        if content_mode == "text":
            type_samples = [s for s in type_samples if not is_image_content(s)]
        elif content_mode == "image":
            type_samples = [s for s in type_samples if is_image_content(s)]
        if sample_size and sample_size < len(type_samples):
            random.seed(seed)
            type_samples = random.sample(type_samples, sample_size)
        selected_ids = set(s["id"] for s in type_samples)

        # ====== 断点续跑读取 ======
        resume_results = {}
        finished_pairs = set()
        if resume_jsonl_path is not None and os.path.exists(resume_jsonl_path):
            with open(resume_jsonl_path, "r", encoding="utf-8") as f:
                for line in f:
                    if line.strip():
                        try:
                            entry = json.loads(line.strip())
                            key = (entry["id"], entry["template_name"])
                            resume_results[key] = entry
                            finished_pairs.add(key)
                        except Exception as e:
                            print(f"读取断点文件出错: {e}")
            print(f"[断点续跑] 已加载 {len(finished_pairs)} 个已完成样本对")
        else:
            print("[断点续跑] 未指定断点文件或文件不存在，全部重新跑")
            resume_results = {}
            finished_pairs = set()

        # ====== 选择模板模式并跑推理评测 ======
        if template_mode == "json":
            # 从json文件读取模板
            template_json_path = os.path.join(PROJECT_ROOT, "code", "template_dict_20250626_180036.json")
            if not os.path.exists(template_json_path):
                print(f"警告：JSON模板文件不存在: {template_json_path}")
                print("自动切换到variable模式...")
                template_mode = "variable"
            else:
                all_templates = load_all_templates_from_json(selected_content_type, template_json_path)
                # 根据配置选择模板
                selected_templates = select_templates_by_config(
                    all_templates,
                    template_selection_mode,
                    template_count,
                    template_selection_seed,
                    template_start_index,
                    template_end_index
                )
                templates_for_eval = {selected_content_type: selected_templates}
                print(f"使用JSON文件中的模板 (原始{len(all_templates)}个，选择{len(selected_templates)}个): {list(templates_for_eval[selected_content_type].keys())}")

        if template_mode == "variable":
            # 直接使用TEMPLATES_IMAGE变量
            if content_mode == "image":
                original_templates = TEMPLATES_IMAGE[selected_content_type]
            else:
                original_templates = TEMPLATES_TEXT[selected_content_type]

            # 根据配置选择模板
            selected_templates = select_templates_by_config(
                original_templates,
                template_selection_mode,
                template_count,
                template_selection_seed,
                template_start_index,
                template_end_index
            )
            templates_for_eval = {selected_content_type: selected_templates}
            print(f"使用TEMPLATES变量中的模板 (原始{len(original_templates)}个，选择{len(selected_templates)}个): {list(templates_for_eval[selected_content_type].keys())}")
        elif template_mode != "json":
            raise ValueError("template_mode 必须是 'json' 或 'variable'")

        if use_multithreading:
            run_template_evaluation_multithread(
                all_samples=all_samples,
                output_path=output_json_path,
                client=client,
                model_name=model_name_use,
                selected_content_type=selected_content_type,
                content_mode=content_mode,
                sample_size=sample_size,
                seed=seed,
                selected_ids=selected_ids,
                finished_pairs=finished_pairs,
                resume_results=resume_results,
                templates=templates_for_eval,
                api_mode=api_mode,
                template_prompt_mode=template_prompt_mode,
                templates_text=TEMPLATES_TEXT,
                templates_image=TEMPLATES_IMAGE,
                api_key=api_key
            )
        else:
            run_template_evaluation_singlethread(
                all_samples=all_samples,
                output_path=output_json_path,
                client=client,
                model_name=model_name_use,
                selected_content_type=selected_content_type,
                content_mode=content_mode,
                sample_size=sample_size,
                seed=seed,
                selected_ids=selected_ids,
                finished_pairs=finished_pairs,
                resume_results=resume_results,
                templates=templates_for_eval,
                api_mode=api_mode,
                template_prompt_mode=template_prompt_mode,
                templates_text=TEMPLATES_TEXT,
                templates_image=TEMPLATES_IMAGE,
                api_key=api_key
            )

        # ====== 评估jsonl并输出准确率md ======
        analyze_results_and_generate_log(
            results_json_path=output_json_path,
            output_log_path=output_log_path,
            templates=templates_for_eval,
            selected_content_type=selected_content_type,
            model_name_use=model_name_use,
            align_sample_ids=False
        )

        analyze_results_and_generate_log(
            results_json_path=output_json_path,
            output_log_path=output_log_path,
            templates=templates_for_eval,
            selected_content_type=selected_content_type,
            model_name_use=model_name_use,
            align_sample_ids=True
        )
    