#!/usr/bin/env python3 """ Скрипт для генерации тестов пересказов из MongoDB. Извлекает текст статьи из коллекции rssNotification (поле .meta.topicContent) и генерирует тестовые данные в формате TXT для бенчмарка AI. """ import argparse import json import sys from pathlib import Path from typing import Dict, Optional import pymongo from pymongo import MongoClient # Добавляем путь к исходникам, чтобы импортировать константы sys.path.insert(0, str(Path(__file__).parent.parent)) from src.constants import TEST_SEPARATOR from src.models.ollama_client import OllamaClient def sanitize_filename(filename: str) -> str: """ Очищает строку от недопустимых символов для имени файла. Args: filename: Исходное имя файла Returns: Очищенное имя файла или пустая строка, если очистка невозможна """ import re # Заменяем недопустимые символы на подчеркивание # Допустимые символы: буквы, цифры, подчеркивание, тире, точка sanitized = re.sub(r'[^\w\-\.]', '_', filename) return sanitized if sanitized else filename def connect_to_mongo() -> MongoClient: """Подключается к MongoDB кластеру.""" client = MongoClient( "mongodb://10.0.0.3:27017,10.0.0.4:27017,10.0.0.5:27017/", connectTimeoutMS=30000, socketTimeoutMS=30000, serverSelectionTimeoutMS=30000, retryWrites=True, retryReads=True ) return client def extract_text_from_topic_content(topic_content: Dict) -> Optional[str]: """ Извлекает текст статьи из .meta.topicContent. Args: topic_content: Содержимое поля .meta.topicContent из MongoDB Returns: Текст статьи или None, если не удалось извлечь """ if not topic_content: return None # Преобразуем в строку, если это не строка content_str = str(topic_content) return content_str def generate_test_from_mongo_record(record_id: str, verbose: bool = True) -> bool: """ Генерирует тест пересказа из записи MongoDB. Args: record_id: ID записи в MongoDB verbose: Выводить подробную отладочную информацию (по умолчанию: True) Returns: True, если тест успешно generated, False в случае ошибки """ try: client = connect_to_mongo() db = client['tracker_conbot'] collection = db['rssNotification'] # Извлекаем запись по ID record = collection.find_one({"_id": record_id}) if not record: if verbose: print(f"❌ Запись с ID {record_id} не найдена в коллекции") return False # Отладочная информация (только при verbose=True) if verbose: print(f"🔍 Найдена запись: {record_id}") print(f"📋 Полная структура записи:") print(json.dumps(record, ensure_ascii=False, indent=2, default=str)) # Извлекаем текст из meta.topicContent meta_data = record.get('meta', {}) topic_content = meta_data.get('topicContent') if not topic_content: if verbose: print(f"❌ В записи {record_id} отсутствует поле meta.topicContent") print(f"📋 Полная структура записи:") print(json.dumps(record, ensure_ascii=False, indent=2, default=str)) return False if verbose: print(f"📝 Тип поля meta.topicContent: {type(topic_content)}") print(f"📝 Содержимое meta.topicContent (первые 500 символов):") print(str(topic_content)[:500]) # Извлекаем текст article_text = extract_text_from_topic_content(topic_content) if not article_text: if verbose: print(f"❌ Не удалось извлечь текст из meta.topicContent записи {record_id}") print(f"📋 Полная структура записи:") print(json.dumps(record, ensure_ascii=False, indent=2, default=str)) return False if verbose: print(f"📝 Итоговый текст (первые 500 символов): {article_text[:500]}") # Генерируем пересказ через LLM (если доступно) expected_summary = "" try: # Создаем клиент Ollama для генерации пересказа ollama_client = OllamaClient(base_url="http://10.0.0.9:11434") # Загружаем промпт для пересказа with open('prompts/summarization.txt', 'r', encoding='utf-8') as f: summarization_prompt = f.read().strip() # Формируем промпт с текстом статьи prompt = summarization_prompt.format(text=article_text) # Генерация пересказа с использованием модели translategemma:4b # Контекст 128000 как указано в задаче expected_summary = ollama_client.generate( model="translategemma:4b", prompt=prompt, num_ctx=128000 ) if verbose: print(f"🤖 Сгенерирован пересказ: {expected_summary[:200]}...") except Exception as e: if verbose: print(f"⚠️ Не удалось сгенерировать пересказ через Ollama: {e}") print(" Будет использован пустой пересказ") # Если генерация не удалась, оставляем пустым (сохраняем обратную совместимость) expected_summary = "" # Создаем директорию для сохранения теста (всегда в tests/summarization) output_path = Path("tests") / "summarization" output_path.mkdir(parents=True, exist_ok=True) # Очищаем ID от недопустимых символов для имени файла filename = sanitize_filename(record_id) if not filename: if verbose: print(f"❌ Не удалось создать допустимое имя файла из ID записи {record_id}") return False # Используем очищенный ID записи как имя файла test_file = output_path / f"{filename}.txt" # Сохраняем текст статьи и ожидаемый пересказ с разделителем with open(test_file, "w", encoding="utf-8") as f: f.write(f"{article_text}{TEST_SEPARATOR}{expected_summary}") if verbose: print(f"✅ Создан тест tests/summarization/{filename}.txt") print(f" Источник: MongoDB запись {record_id}") print(f" Текст статьи (первые 100 символов): {article_text[:100]}...") return True except Exception as e: if verbose: print(f"❌ Ошибка при генерации теста: {e}") try: record = collection.find_one({"_id": record_id}) if record: print(f"📋 Полная структура записи:") print(json.dumps(record, ensure_ascii=False, indent=2, default=str)) except: pass return False finally: if 'client' in locals(): client.close() def validate_test(test_data: Dict[str, str]) -> bool: """Валидирует тестовые данные.""" if not isinstance(test_data, dict): print("❌ Тест должен быть словарём (JSON объект)") return False if "prompt" not in test_data: print("❌ Отсутствует поле 'prompt'") return False if "expected" not in test_data: print("❌ Отсутствует поле 'expected'") return False if not isinstance(test_data["prompt"], str): print("❌ Поле 'prompt' должно быть строкой") return False if not isinstance(test_data["expected"], str): print("❌ Поле 'expected' должно быть строкой") return False if not test_data["prompt"].strip(): print("❌ Поле 'prompt' не может быть пустым") return False if not test_data["expected"].strip(): print("❌ Поле 'expected' не может быть пустым") return False return True def read_ids_from_file(file_path: str) -> list: """ Читает ID записей из файла. Args: file_path: Путь к файлу с ID записей (по одной на строку) Returns: Список ID записей """ try: with open(file_path, 'r', encoding='utf-8') as f: ids = [line.strip() for line in f if line.strip()] return ids except Exception as e: print(f"❌ Ошибка при чтении файла {file_path}: {e}") return [] def main(): """Основная функция скрипта.""" parser = argparse.ArgumentParser( description="Генератор тестов пересказов из MongoDB", epilog="Примеры использования:\n" " python scripts/generate_summarization_from_mongo.py --record-id 507f1f77bcf86cd799439011\n" " python scripts/generate_summarization_from_mongo.py --id-file ids.txt" ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "--record-id", type=str, help="ID записи в MongoDB (для обработки одной записи)" ) group.add_argument( "--id-file", type=str, help="Файл с ID записей (по одной на строку, для обработки нескольких записей)" ) args = parser.parse_args() if args.record_id: print(f"🔍 Подключаюсь к MongoDB кластеру...") print(f"📄 Извлекаю запись с ID: {args.record_id}") print(f"💾 Сохраняю тест в: tests/summarization/") success = generate_test_from_mongo_record(args.record_id) if success: print("\n✨ Готово! Тест успешно generated.") else: print("\n❌ Не удалось generated тест.") sys.exit(1) elif args.id_file: print(f"🔍 Подключаюсь к MongoDB кластеру...") print(f"📄 Извлекаю ID записи из файла: {args.id_file}") print(f"💾 Сохраняю тесты в: tests/summarization/") # Читаем ID из файла record_ids = read_ids_from_file(args.id_file) if not record_ids: print("❌ Файл с ID записей пуст или недействителен.") sys.exit(1) print(f"📊 Найдено {len(record_ids)} ID записей в файле") print("🔄 Начинаю обработку записей...\n") success_count = 0 error_count = 0 for i, record_id in enumerate(record_ids, 1): print(f"[{i}/{len(record_ids)}] Обрабатываю запись: {record_id}") success = generate_test_from_mongo_record(record_id, verbose=True) if success: success_count += 1 else: error_count += 1 print() # Пустая строка для разделения логов print(f"\n📊 Итог:") print(f" ✅ Успешно generated: {success_count}") print(f" ❌ Ошибки: {error_count}") if error_count > 0: print(f"\n⚠️ Некоторые записи были обработаны с ошибками. Проверьте логи выше.") sys.exit(1) else: print("\n✨ Готово! Все тесты успешно generated.") if __name__ == "__main__": main()