diff --git a/rag/config_models.py b/rag/config_models.py new file mode 100644 index 0000000..0b2449b --- /dev/null +++ b/rag/config_models.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +""" +Модели данных для конфигурации на основе Pydantic. +Используются для загрузки и валидации настроек из YAML-файлов (rag.conf и bot.conf). +Обеспечивают строгую типизацию и автоматическую проверку обязательных полей. +""" + +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, Field, field_validator +from pathlib import Path + +# ============================================================ +# Базовые модели для секций конфигурации +# ============================================================ + +class DatabaseConfig(BaseModel): + """Настройки подключения к PostgreSQL.""" + host: str = Field(..., description="Хост базы данных") + port: int = Field(5432, description="Порт PostgreSQL") + user: str = Field(..., description="Имя пользователя") + password_env: str = Field(..., description="Имя переменной окружения с паролем") + database: str = Field(..., description="Имя базы данных") + ssl: bool = Field(False, description="Использовать SSL") + ssl_mode: Optional[str] = Field("prefer", description="Режим SSL (require, prefer, disable)") + + @field_validator('port') + def validate_port(cls, v): + if not 1 <= v <= 65535: + raise ValueError('port must be between 1 and 65535') + return v + + +class QdrantConfig(BaseModel): + """Настройки векторной базы Qdrant.""" + host: str = Field(..., description="Хост Qdrant") + port: int = Field(6333, description="HTTP порт Qdrant") + grpc_port: int = Field(6334, description="gRPC порт Qdrant") + collection: str = Field(..., description="Имя коллекции") + vector_size: int = Field(1024, description="Размерность dense-векторов") + distance: str = Field("Cosine", description="Метрика расстояния (Cosine или Euclid)") + + +class GigaChatConfig(BaseModel): + """Настройки GigaChat API.""" + credentials_env: str = Field(..., description="Имя переменной окружения с API-ключом") + model_embedding: str = Field("Embeddings", description="Модель для эмбеддингов") + model_generation: str = Field("GigaChat-Max", description="Модель для генерации") + timeout: int = Field(600, description="Таймаут запросов в секундах") + temperature: float = Field(0.1, description="Температура генерации по умолчанию") + + +class EmbeddingConfig(BaseModel): + """Настройки кэширования эмбеддингов.""" + cache_size: int = Field(4096, description="Размер кэша (количество элементов)") + verify_ssl_certs: bool = Field(False, description="Проверять SSL-сертификаты") + model: str = Field("Embeddings", description="Модель эмбеддингов") + timeout: int = Field(30, description="Таймаут запроса эмбеддингов") + + +class ChunkingConfig(BaseModel): + """Настройки разбиения документов на чанки.""" + enabled: bool = Field(True, description="Включить чанкинг") + chunk_size_tokens: int = Field(200, description="Размер чанка в токенах") + overlap_tokens: int = Field(50, description="Перекрытие между чанками в токенах") + strategy: str = Field("recursive_split_by_sentences", description="Стратегия разбиения") + approx_chunk_chars: int = Field(500, description="Приблизительный размер чанка в символах (fallback)") + approx_overlap_chars: int = Field(100, description="Приблизительное перекрытие в символах (fallback)") + + +class RAGConfig(BaseModel): + """Параметры RAG-пайплайна.""" + default_top_k: int = Field(30, description="Количество фрагментов по умолчанию") + metrics_top_k: int = Field(30, description="Количество фрагментов для извлечения метрик") + contradiction_top_k: int = Field(10, description="Количество фрагментов для проверки противоречий") + max_context_tokens: int = Field(3000, description="Максимальное количество токенов контекста") + reserved_for_answer_tokens: int = Field(1000, description="Резерв токенов для ответа") + reserved_for_overhead_tokens: int = Field(200, description="Резерв для служебных нужд") + max_model_tokens: int = Field(8192, description="Максимальный размер контекстного окна модели") + rerank_min_length: int = Field(5000, description="Минимальная длина контекста для переранжирования (символы)") + + +class HTTPServerConfig(BaseModel): + """Настройки HTTP-сервера.""" + host: str = Field("0.0.0.0", description="Хост для привязки") + port: int = Field(8080, description="Порт для привязки") + + +class HTTPConfig(BaseModel): + """Таймауты HTTP-запросов.""" + download_timeout: int = Field(30, description="Таймаут скачивания") + upload_timeout: int = Field(60, description="Таймаут загрузки") + + +class SummarizationConfig(BaseModel): + """Настройки иерархического резюмирования.""" + enable_hierarchical_summarization: bool = Field(True, description="Включить сжатие") + max_tokens_for_document: int = Field(8000, description="Порог для сжатия документа (токены)") + target_tokens_after_summary: int = Field(3000, description="Целевой размер после сжатия") + chunk_size_tokens: int = Field(500, description="Размер блока для резюмирования") + max_depth: int = Field(2, description="Максимальная глубина рекурсии") + + +class PathsConfig(BaseModel): + """Пути к каталогам.""" + data_dir: str = Field("data", description="Каталог для постоянных данных") + temp_dir: str = Field("temp", description="Каталог для временных файлов") + prompts_dir: str = Field("prompts", description="Каталог с промптами") + upload_dir: str = Field("upload", description="Каталог для загрузок") + log_dir: str = Field("logs", description="Каталог для логов") + log_filename: str = Field("{name}.log", description="Шаблон имени лог-файла") + + +class FeaturesConfig(BaseModel): + """Включение/отключение функций.""" + file_processing: bool = Field(True, description="Обработка файлов") + surgical_replace: bool = Field(True, description="Хирургическая замена") + voice_recognition: bool = Field(True, description="Распознавание голоса") + vision: bool = Field(True, description="OCR") + archive_support: bool = Field(True, description="Поддержка архивов") + ocr: bool = Field(True, description="Распознавание текста на изображениях") + allow_public_knowledge: bool = Field(False, description="Разрешить использование общих знаний") + max_file_size_mb: int = Field(100, description="Максимальный размер файла в МБ") + max_archive_files: int = Field(100, description="Максимальное количество файлов в архиве") + enable_intent_classification: bool = Field(True, description="Включить классификацию намерений") + enable_self_critique: bool = Field(True, description="Включить самокритику") + surgical_keywords: List[str] = Field(["замени", "заменить"], description="Ключевые слова для хирургической замены") + mention_keyword: Optional[str] = Field(None, description="Ключевое слово для упоминания бота в группе") + + +class CleanupConfig(BaseModel): + """Настройки очистки старых файлов.""" + temp_days: int = Field(3, description="Срок хранения временных файлов (дни)") + upload_days: int = Field(7, description="Срок хранения загруженных файлов (дни)") + interval_seconds: int = Field(3600, description="Интервал очистки (секунды)") + user_data_days: int = Field(30, description="Срок хранения пользовательских данных (дни)") + + +class FilePermissionsConfig(BaseModel): + """Права доступа к создаваемым файлам.""" + mode: str = Field("0644", description="Права в восьмеричном формате (строка)") + + +class AIConfig(BaseModel): + """Настройки AI (температура, таймауты).""" + model: Optional[str] = Field(None, description="Модель генерации (если не указана, берётся из GigaChatConfig)") + temperature: float = Field(0.1, description="Температура генерации") + timeout: int = Field(600, description="Таймаут генерации") + prompts: Dict[str, str] = Field(..., description="Имена файлов промптов (ключ -> имя файла)") + + +class PromptTemperatures(BaseModel): + """Температуры для отдельных функций.""" + temperature: float = Field(0.1, description="Температура") + + +class PromptsConfig(BaseModel): + """Секция с температурами для разных функций.""" + intent: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + expand: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + metrics: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + summary: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + summary_max_chars: int = Field(8000, description="Максимальная длина текста для суммаризации") + consistency: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + consistency_max_fragments: int = Field(5, description="Максимальное количество фрагментов для проверки противоречий") + critique: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + rerank: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + rerank_min_length: int = Field(5000, description="Минимальная длина контекста для переранжирования") + generate_document: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1)) + + +# ============================================================ +# Главная модель конфигурации приложения +# ============================================================ + +class AppConfig(BaseModel): + """ + Полная конфигурация приложения. + Объединяет все секции. + """ + name: str = Field(..., description="Имя профиля бота") + jid: str = Field(..., description="JID бота (user@domain)") + password_env: str = Field(..., description="Имя переменной окружения с паролем XMPP") + resource: str = Field("bot", description="Ресурс XMPP") + status_message: str = Field(..., description="Сообщение статуса присутствия") + xmpp_server: Optional[str] = Field(None, description="XMPP сервер (если не указан, берётся из JID)") + xmpp_port: int = Field(5222, description="Порт XMPP") + xmpp_use_ssl: bool = Field(False, description="Использовать SSL для XMPP") + admin_jids: List[str] = Field([], description="Список JID администраторов") + log_level: str = Field("INFO", description="Уровень логирования") + rag_server_url: str = Field("http://localhost:8080", description="URL RAG-сервера") + + # Секции + database: DatabaseConfig + qdrant: QdrantConfig + gigachat: GigaChatConfig + embedding: EmbeddingConfig + chunking: ChunkingConfig + rag: RAGConfig + http_server: HTTPServerConfig + http: HTTPConfig + summarization: SummarizationConfig + paths: PathsConfig + features: FeaturesConfig + cleanup: CleanupConfig + file_permissions: FilePermissionsConfig + ai: AIConfig + prompts: PromptsConfig + + # Дополнительные вычисляемые поля (заполняются при загрузке) + xmpp_password: Optional[str] = Field(None, description="Пароль XMPP (загружается из окружения)") + gigachat_api_key: Optional[str] = Field(None, description="API-ключ GigaChat (из окружения)") + db_password: Optional[str] = Field(None, description="Пароль PostgreSQL (из окружения)") + salute_speech_auth: Optional[str] = Field(None, description="Авторизация для SaluteSpeech (из окружения)") + + # Пути (преобразуются в Path при загрузке) + profile_dir: Optional[Path] = Field(None, description="Корень профиля") + data_dir_path: Optional[Path] = Field(None) + temp_dir_path: Optional[Path] = Field(None) + prompts_dir_path: Optional[Path] = Field(None) + upload_dir_path: Optional[Path] = Field(None) + log_dir_path: Optional[Path] = Field(None) + log_file_path: Optional[Path] = Field(None) + + # Промпты (загружаются из файлов) + prompts_content: Optional[Dict[str, str]] = Field(default_factory=dict, description="Содержимое промптов") + + class Config: + extra = "forbid" # Запрещаем неизвестные поля \ No newline at end of file