- /rag/config_models.py - /rag/utils/config_loader.py - /rag/prompt_builder.py - /rag/query_processor.py - /template_bot_profile/data/fewshot_examples.json
546 lines
23 KiB
Python
546 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Модели данных для конфигурации на основе Pydantic.
|
||
Используются для загрузки и валидации настроек из YAML-файлов (rag.conf и bot.conf).
|
||
Обеспечивают строгую типизацию и автоматическую проверку обязательных полей.
|
||
|
||
Добавлены прокси-свойства для обратной совместимости с BotConfig.
|
||
"""
|
||
|
||
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="Имя пользователя")
|
||
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."""
|
||
api_key: Optional[str] = Field(None, 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))
|
||
generate_document: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1))
|
||
|
||
|
||
class WebScraperConfig(BaseModel):
|
||
"""Настройки веб-скрапера."""
|
||
max_pages: int = Field(5, description="Максимальное количество страниц для рекурсивного обхода")
|
||
max_depth: int = Field(1, description="Максимальная глубина рекурсии")
|
||
|
||
|
||
class AuthConfig(BaseModel):
|
||
"""Настройки аутентификации."""
|
||
api_key_env: str = Field("RAG_API_KEY", description="Имя переменной окружения с API-ключом для доступа к RAG-серверу")
|
||
|
||
|
||
class FewshotConfig(BaseModel):
|
||
"""Настройки few-shot примеров."""
|
||
max_examples: int = Field(3, description="Максимальное количество примеров, включаемых в промт")
|
||
examples_file: str = Field("data/fewshot_examples.json", description="Путь к файлу с примерами (относительно профиля)")
|
||
|
||
|
||
# ============================================================
|
||
# Главная модель конфигурации приложения
|
||
# ============================================================
|
||
|
||
class AppConfig(BaseModel):
|
||
"""
|
||
Полная конфигурация приложения.
|
||
Объединяет все секции.
|
||
"""
|
||
name: str = Field(..., description="Имя профиля бота")
|
||
jid: str = Field(..., description="JID бота (user@domain)")
|
||
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-сервера")
|
||
xmpp_password: Optional[str] = Field(None, description="Пароль XMPP (из окружения)")
|
||
salute_speech_auth: Optional[str] = Field(None, description="Авторизация для SaluteSpeech (из окружения)")
|
||
|
||
# Секции
|
||
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
|
||
web_scraper: WebScraperConfig = Field(default_factory=WebScraperConfig)
|
||
auth: AuthConfig = Field(default_factory=AuthConfig)
|
||
fewshot: FewshotConfig = Field(default_factory=FewshotConfig)
|
||
|
||
# Дополнительные поля для путей (заполняются при загрузке)
|
||
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="Содержимое промптов")
|
||
|
||
# Приватное поле для API-ключа RAG (не сериализуется)
|
||
_rag_api_key: Optional[str] = Field(None, description="API-ключ RAG-сервера", exclude=True)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Прокси-свойства для обратной совместимости со старым BotConfig
|
||
# ------------------------------------------------------------------
|
||
|
||
@property
|
||
def rag_api_key(self) -> Optional[str]:
|
||
return self._rag_api_key
|
||
|
||
@property
|
||
def db_host(self) -> str:
|
||
return self.database.host
|
||
|
||
@property
|
||
def db_port(self) -> int:
|
||
return self.database.port
|
||
|
||
@property
|
||
def db_user(self) -> str:
|
||
return self.database.user
|
||
|
||
@property
|
||
def db_password(self) -> str:
|
||
return self.database.password
|
||
|
||
@property
|
||
def db_name(self) -> str:
|
||
return self.database.database
|
||
|
||
@property
|
||
def db_ssl(self) -> bool:
|
||
return self.database.ssl
|
||
|
||
@property
|
||
def db_ssl_mode(self) -> Optional[str]:
|
||
return self.database.ssl_mode
|
||
|
||
@property
|
||
def qdrant_host(self) -> str:
|
||
return self.qdrant.host
|
||
|
||
@property
|
||
def qdrant_port(self) -> int:
|
||
return self.qdrant.port
|
||
|
||
@property
|
||
def qdrant_grpc_port(self) -> int:
|
||
return self.qdrant.grpc_port
|
||
|
||
@property
|
||
def qdrant_collection(self) -> str:
|
||
return self.qdrant.collection
|
||
|
||
@property
|
||
def qdrant_vector_size(self) -> int:
|
||
return self.qdrant.vector_size
|
||
|
||
@property
|
||
def qdrant_distance(self) -> str:
|
||
return self.qdrant.distance
|
||
|
||
@property
|
||
def gigachat_api_key(self) -> Optional[str]:
|
||
return self.gigachat.api_key
|
||
|
||
@property
|
||
def gigachat_model_embedding(self) -> str:
|
||
return self.gigachat.model_embedding
|
||
|
||
@property
|
||
def gigachat_model_generation(self) -> str:
|
||
return self.gigachat.model_generation
|
||
|
||
@property
|
||
def gigachat_timeout(self) -> int:
|
||
return self.gigachat.timeout
|
||
|
||
@property
|
||
def gigachat_temperature(self) -> float:
|
||
return self.gigachat.temperature
|
||
|
||
@property
|
||
def embedding_cache_size(self) -> int:
|
||
return self.embedding.cache_size
|
||
|
||
@property
|
||
def embedding_model(self) -> str:
|
||
return self.embedding.model
|
||
|
||
@property
|
||
def embedding_timeout(self) -> int:
|
||
return self.embedding.timeout
|
||
|
||
@property
|
||
def embedding_verify_ssl(self) -> bool:
|
||
return self.embedding.verify_ssl_certs
|
||
|
||
@property
|
||
def chunking_enabled(self) -> bool:
|
||
return self.chunking.enabled
|
||
|
||
@property
|
||
def chunk_size_tokens(self) -> int:
|
||
return self.chunking.chunk_size_tokens
|
||
|
||
@property
|
||
def overlap_tokens(self) -> int:
|
||
return self.chunking.overlap_tokens
|
||
|
||
@property
|
||
def chunking_strategy(self) -> str:
|
||
return self.chunking.strategy
|
||
|
||
@property
|
||
def chunking_approx_chunk_chars(self) -> int:
|
||
return self.chunking.approx_chunk_chars
|
||
|
||
@property
|
||
def chunking_approx_overlap_chars(self) -> int:
|
||
return self.chunking.approx_overlap_chars
|
||
|
||
@property
|
||
def rag_default_top_k(self) -> int:
|
||
return self.rag.default_top_k
|
||
|
||
@property
|
||
def rag_metrics_top_k(self) -> int:
|
||
return self.rag.metrics_top_k
|
||
|
||
@property
|
||
def rag_contradiction_top_k(self) -> int:
|
||
return self.rag.contradiction_top_k
|
||
|
||
@property
|
||
def max_context_tokens(self) -> int:
|
||
return self.rag.max_context_tokens
|
||
|
||
@property
|
||
def reserved_for_answer_tokens(self) -> int:
|
||
return self.rag.reserved_for_answer_tokens
|
||
|
||
@property
|
||
def reserved_for_overhead_tokens(self) -> int:
|
||
return self.rag.reserved_for_overhead_tokens
|
||
|
||
@property
|
||
def max_model_tokens(self) -> int:
|
||
return self.rag.max_model_tokens
|
||
|
||
@property
|
||
def rerank_min_length(self) -> int:
|
||
return self.rag.rerank_min_length
|
||
|
||
@property
|
||
def http_download_timeout(self) -> int:
|
||
return self.http.download_timeout
|
||
|
||
@property
|
||
def http_upload_timeout(self) -> int:
|
||
return self.http.upload_timeout
|
||
|
||
@property
|
||
def file_processing(self) -> bool:
|
||
return self.features.file_processing
|
||
|
||
@property
|
||
def surgical_replace(self) -> bool:
|
||
return self.features.surgical_replace
|
||
|
||
@property
|
||
def voice_recognition(self) -> bool:
|
||
return self.features.voice_recognition
|
||
|
||
@property
|
||
def vision(self) -> bool:
|
||
return self.features.vision
|
||
|
||
@property
|
||
def archive_support(self) -> bool:
|
||
return self.features.archive_support
|
||
|
||
@property
|
||
def ocr(self) -> bool:
|
||
return self.features.ocr
|
||
|
||
@property
|
||
def allow_public_knowledge(self) -> bool:
|
||
return self.features.allow_public_knowledge
|
||
|
||
@property
|
||
def max_file_size_mb(self) -> int:
|
||
return self.features.max_file_size_mb
|
||
|
||
@property
|
||
def max_archive_files(self) -> int:
|
||
return self.features.max_archive_files
|
||
|
||
@property
|
||
def enable_intent_classification(self) -> bool:
|
||
return self.features.enable_intent_classification
|
||
|
||
@property
|
||
def enable_self_critique(self) -> bool:
|
||
return self.features.enable_self_critique
|
||
|
||
@property
|
||
def surgical_keywords(self) -> List[str]:
|
||
return self.features.surgical_keywords
|
||
|
||
@property
|
||
def mention_keyword(self) -> Optional[str]:
|
||
return self.features.mention_keyword
|
||
|
||
@property
|
||
def ai_model(self) -> str:
|
||
return self.ai.model or self.gigachat.model_generation
|
||
|
||
@property
|
||
def ai_temperature(self) -> float:
|
||
return self.ai.temperature
|
||
|
||
@property
|
||
def ai_timeout(self) -> int:
|
||
return self.ai.timeout
|
||
|
||
@property
|
||
def temp_cleanup_days(self) -> int:
|
||
return self.cleanup.temp_days
|
||
|
||
@property
|
||
def upload_cleanup_days(self) -> int:
|
||
return self.cleanup.upload_days
|
||
|
||
@property
|
||
def cleanup_interval(self) -> int:
|
||
return self.cleanup.interval_seconds
|
||
|
||
@property
|
||
def user_data_cleanup_days(self) -> int:
|
||
return self.cleanup.user_data_days
|
||
|
||
@property
|
||
def ws_max_pages(self) -> int:
|
||
return self.web_scraper.max_pages
|
||
|
||
@property
|
||
def ws_max_depth(self) -> int:
|
||
return self.web_scraper.max_depth
|
||
|
||
@property
|
||
def intent_temperature(self) -> float:
|
||
return self.prompts.intent.temperature
|
||
|
||
@property
|
||
def expand_temperature(self) -> float:
|
||
return self.prompts.expand.temperature
|
||
|
||
@property
|
||
def metrics_temperature(self) -> float:
|
||
return self.prompts.metrics.temperature
|
||
|
||
@property
|
||
def summary_temperature(self) -> float:
|
||
return self.prompts.summary.temperature
|
||
|
||
@property
|
||
def summary_max_chars(self) -> int:
|
||
return self.prompts.summary_max_chars
|
||
|
||
@property
|
||
def consistency_temperature(self) -> float:
|
||
return self.prompts.consistency.temperature
|
||
|
||
@property
|
||
def consistency_max_fragments(self) -> int:
|
||
return self.prompts.consistency_max_fragments
|
||
|
||
@property
|
||
def critique_temperature(self) -> float:
|
||
return self.prompts.critique.temperature
|
||
|
||
@property
|
||
def rerank_temperature(self) -> float:
|
||
return self.prompts.rerank.temperature
|
||
|
||
@property
|
||
def generate_document_temperature(self) -> float:
|
||
return self.prompts.generate_document.temperature
|
||
|
||
@property
|
||
def file_mode(self) -> int:
|
||
return int(self.file_permissions.mode, 8)
|
||
|
||
@property
|
||
def max_fewshot_examples(self) -> int:
|
||
return self.fewshot.max_examples
|
||
|
||
@property
|
||
def fewshot_examples_file(self) -> str:
|
||
return self.fewshot.examples_file
|
||
|
||
class Config:
|
||
extra = "forbid" # Запрещаем неизвестные поля |