| import json |
| import os |
| import time |
| import argparse |
| from pathlib import Path |
| from typing import List, Dict, Optional |
| from tqdm import tqdm |
| import hashlib |
|
|
| import sys |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| from opencompass.models.deepseek_api import DeepseekAPI |
|
|
|
|
| class QueryVariantGenerator: |
| |
| def __init__( |
| self, |
| api_key: str, |
| api_url: str = "https://api.deepseek.com/v1/chat/completions", |
| model: str = "deepseek-chat", |
| cache_dir: str = "./cache/query_variants", |
| num_variants: int = 3, |
| ): |
| self.api = DeepseekAPI( |
| path=model, |
| key=api_key, |
| url=api_url, |
| query_per_second=1, |
| retry=3, |
| ) |
| |
| self.cache_dir = Path(cache_dir) |
| self.cache_dir.mkdir(parents=True, exist_ok=True) |
| self.cache_file = self.cache_dir / "query_variants_cache.json" |
| |
| self.num_variants = num_variants |
| self.cache = self._load_cache() |
| |
| def _load_cache(self) -> Dict: |
| if self.cache_file.exists(): |
| with open(self.cache_file, 'r', encoding='utf-8') as f: |
| return json.load(f) |
| return {} |
| |
| def _save_cache(self): |
| with open(self.cache_file, 'w', encoding='utf-8') as f: |
| json.dump(self.cache, f, ensure_ascii=False, indent=2) |
| |
| def _get_cache_key(self, query: str, variant_type: str) -> str: |
| content = f"{query}_{variant_type}_{self.num_variants}" |
| return hashlib.md5(content.encode()).hexdigest() |
| |
| def _create_rewrite_prompts(self) -> List[Dict]: |
| prompts = [ |
| { |
| "name": "paraphrase", |
| "instruction": """Please paraphrase the following question while keeping its exact meaning and intent. |
| Rules: |
| 1. Maintain the same semantic meaning |
| 2. Change the sentence structure and word choice |
| 3. Keep the question type (what, who, when, etc.) if possible |
| 4. Do not add or remove information |
| 5. Only output the paraphrased question, nothing else |
| |
| Original question: {query} |
| |
| Paraphrased question:""" |
| }, |
| { |
| "name": "rephrase_formal", |
| "instruction": """Please rephrase the following question in a more formal academic style while preserving its meaning. |
| Rules: |
| 1. Use more formal vocabulary |
| 2. Maintain the exact same meaning |
| 3. Change sentence structure appropriately |
| 4. Only output the rephrased question |
| |
| Question: {query} |
| |
| Formal version:""" |
| }, |
| { |
| "name": "rephrase_conversational", |
| "instruction": """Please rewrite the following question in a conversational, natural language style while keeping the same meaning. |
| Rules: |
| 1. Use everyday language |
| 2. Make it sound natural and conversational |
| 3. Preserve the core question |
| 4. Only output the conversational version |
| |
| Question: {query} |
| |
| Conversational version:""" |
| }, |
| { |
| "name": "restructure", |
| "instruction": """Please restructure the following question using different syntax while maintaining identical meaning. |
| Rules: |
| 1. Change word order or sentence structure |
| 2. Use synonyms where appropriate |
| 3. Keep the same information content |
| 4. Only output the restructured question |
| |
| Original: {query} |
| |
| Restructured:""" |
| }, |
| { |
| "name": "simplify", |
| "instruction": """Please simplify the following question using simpler words while keeping the exact same meaning. |
| Rules: |
| 1. Use simpler, more common words |
| 2. Maintain the same question intent |
| 3. Don't lose any information |
| 4. Only output the simplified question |
| |
| Question: {query} |
| |
| Simplified:""" |
| }, |
| ] |
| |
| return prompts |
| |
| def generate_variants( |
| self, |
| query: str, |
| use_cache: bool = True |
| ) -> List[str]: |
| """ |
| 生成query的变体 |
| |
| Args: |
| query: 原始查询 |
| use_cache: 是否使用缓存 |
| |
| Returns: |
| query变体列表 (不包含原始query) |
| """ |
| variants = [] |
| prompts = self._create_rewrite_prompts() |
| |
| selected_prompts = prompts[:self.num_variants] |
| |
| for prompt_config in selected_prompts: |
| variant_type = prompt_config["name"] |
| cache_key = self._get_cache_key(query, variant_type) |
| |
| if use_cache and cache_key in self.cache: |
| variant = self.cache[cache_key] |
| print(f"✓ 从缓存加载: {variant_type}") |
| else: |
| prompt = prompt_config["instruction"].format(query=query) |
| |
| try: |
| variant = self.api._generate(prompt, max_out_len=256) |
| variant = variant.strip() |
| |
| prefixes_to_remove = [ |
| "Paraphrased question:", |
| "Formal version:", |
| "Conversational version:", |
| "Restructured:", |
| "Simplified:", |
| ] |
| for prefix in prefixes_to_remove: |
| if variant.startswith(prefix): |
| variant = variant[len(prefix):].strip() |
| |
| self.cache[cache_key] = variant |
| self._save_cache() |
| |
| print(f"✓ API生成: {variant_type}") |
| |
| time.sleep(1) |
| |
| except Exception as e: |
| print(f"✗ 生成失败 ({variant_type}): {e}") |
| variant = query |
| |
| if variant and variant != query: |
| variants.append(variant) |
| |
| return variants |
| |
| def process_dataset( |
| self, |
| input_path: str, |
| output_path: str, |
| max_samples: Optional[int] = None, |
| ): |
| """ |
| 处理整个数据集 |
| |
| Args: |
| input_path: 输入数据文件路径 |
| output_path: 输出文件路径 |
| max_samples: 最大处理样本数 |
| """ |
| print(f"\n{'='*60}") |
| print(f"开始处理数据集: {input_path}") |
| print(f"生成 {self.num_variants} 个query变体") |
| print(f"{'='*60}\n") |
| |
| with open(input_path, 'r', encoding='utf-8') as f: |
| if input_path.endswith('.jsonl'): |
| data = [json.loads(line) for line in f] |
| else: |
| data = json.load(f) |
| |
| if max_samples: |
| data = data[:max_samples] |
| |
| augmented_data = [] |
| |
| for idx, item in enumerate(tqdm(data, desc="生成query variants")): |
| query = item.get('question', item.get('query', '')) |
| |
| if not query: |
| print(f"警告: 样本 {idx} 缺少query,跳过") |
| continue |
| |
| print(f"\n[{idx+1}/{len(data)}] 原始Query: {query[:100]}...") |
| variants = self.generate_variants(query, use_cache=True) |
| |
| print(f" 生成了 {len(variants)} 个变体:") |
| for i, var in enumerate(variants, 1): |
| print(f" {i}. {var[:80]}...") |
| |
| augmented_item = { |
| **item, |
| 'query_original': query, |
| 'query_variants': variants, |
| 'num_variants': len(variants), |
| } |
| |
| augmented_data.append(augmented_item) |
| |
| output_dir = os.path.dirname(output_path) |
| if output_dir: |
| os.makedirs(output_dir, exist_ok=True) |
| |
| with open(output_path, 'w', encoding='utf-8') as f: |
| json.dump(augmented_data, f, ensure_ascii=False, indent=2) |
| |
| print(f"\n{'='*60}") |
| print(f"✓ 处理完成!") |
| print(f" 输入: {len(data)} 个样本") |
| print(f" 输出: {len(augmented_data)} 个样本") |
| print(f" 保存到: {output_path}") |
| print(f" 缓存: {len(self.cache)} 个entries") |
| print(f"{'='*60}\n") |
| |
| return augmented_data |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser( |
| description="使用DeepSeek API生成Query Variants" |
| ) |
| |
| parser.add_argument( |
| "--api_key", |
| type=str, |
| required=True, |
| help="DeepSeek API Key" |
| ) |
| parser.add_argument( |
| "--api_url", |
| type=str, |
| default="https://api.deepseek.com/v1/chat/completions", |
| help="DeepSeek API URL" |
| ) |
| parser.add_argument( |
| "--model", |
| type=str, |
| default="deepseek-chat", |
| help="模型名称" |
| ) |
| |
| parser.add_argument( |
| "--input", |
| type=str, |
| required=True, |
| help="输入数据文件 (JSON/JSONL)" |
| ) |
| parser.add_argument( |
| "--output", |
| type=str, |
| required=True, |
| help="输出文件路径" |
| ) |
| parser.add_argument( |
| "--max_samples", |
| type=int, |
| default=None, |
| help="最大处理样本数" |
| ) |
| |
| parser.add_argument( |
| "--num_variants", |
| type=int, |
| default=3, |
| help="每个query生成的变体数量" |
| ) |
| parser.add_argument( |
| "--cache_dir", |
| type=str, |
| default="./cache/query_variants", |
| help="缓存目录" |
| ) |
| |
| args = parser.parse_args() |
| |
| generator = QueryVariantGenerator( |
| api_key=args.api_key, |
| api_url=args.api_url, |
| model=args.model, |
| cache_dir=args.cache_dir, |
| num_variants=args.num_variants, |
| ) |
| |
| generator.process_dataset( |
| input_path=args.input, |
| output_path=args.output, |
| max_samples=args.max_samples, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|
|
|