Files
fckbot/rag/config_models.py
Markov Andrey 67d86d6c1d Update 5 files
- /rag/config_models.py
- /rag/utils/config_loader.py
- /rag/prompt_builder.py
- /rag/query_processor.py
- /template_bot_profile/data/fewshot_examples.json
2026-06-30 14:57:03 +00:00

546 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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" # Запрещаем неизвестные поля