Update 5 files
- /rag/config_models.py - /rag/utils/config_loader.py - /rag/rag_orchestrator.py - /rag/rag_server.py - /rag/rag_api.py
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
Модели данных для конфигурации на основе Pydantic.
|
Модели данных для конфигурации на основе Pydantic.
|
||||||
Используются для загрузки и валидации настроек из YAML-файлов (rag.conf и bot.conf).
|
Используются для загрузки и валидации настроек из YAML-файлов (rag.conf и bot.conf).
|
||||||
Обеспечивают строгую типизацию и автоматическую проверку обязательных полей.
|
Обеспечивают строгую типизацию и автоматическую проверку обязательных полей.
|
||||||
|
|
||||||
|
Добавлены прокси-свойства для обратной совместимости с BotConfig.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
@@ -18,7 +20,6 @@ class DatabaseConfig(BaseModel):
|
|||||||
host: str = Field(..., description="Хост базы данных")
|
host: str = Field(..., description="Хост базы данных")
|
||||||
port: int = Field(5432, description="Порт PostgreSQL")
|
port: int = Field(5432, description="Порт PostgreSQL")
|
||||||
user: str = Field(..., description="Имя пользователя")
|
user: str = Field(..., description="Имя пользователя")
|
||||||
password_env: str = Field(..., description="Имя переменной окружения с паролем")
|
|
||||||
database: str = Field(..., description="Имя базы данных")
|
database: str = Field(..., description="Имя базы данных")
|
||||||
ssl: bool = Field(False, description="Использовать SSL")
|
ssl: bool = Field(False, description="Использовать SSL")
|
||||||
ssl_mode: Optional[str] = Field("prefer", description="Режим SSL (require, prefer, disable)")
|
ssl_mode: Optional[str] = Field("prefer", description="Режим SSL (require, prefer, disable)")
|
||||||
@@ -42,7 +43,7 @@ class QdrantConfig(BaseModel):
|
|||||||
|
|
||||||
class GigaChatConfig(BaseModel):
|
class GigaChatConfig(BaseModel):
|
||||||
"""Настройки GigaChat API."""
|
"""Настройки GigaChat API."""
|
||||||
credentials_env: str = Field(..., description="Имя переменной окружения с API-ключом")
|
api_key: Optional[str] = Field(None, description="API-ключ (загружается из окружения)")
|
||||||
model_embedding: str = Field("Embeddings", description="Модель для эмбеддингов")
|
model_embedding: str = Field("Embeddings", description="Модель для эмбеддингов")
|
||||||
model_generation: str = Field("GigaChat-Max", description="Модель для генерации")
|
model_generation: str = Field("GigaChat-Max", description="Модель для генерации")
|
||||||
timeout: int = Field(600, description="Таймаут запросов в секундах")
|
timeout: int = Field(600, description="Таймаут запросов в секундах")
|
||||||
@@ -164,10 +165,15 @@ class PromptsConfig(BaseModel):
|
|||||||
consistency_max_fragments: int = Field(5, description="Максимальное количество фрагментов для проверки противоречий")
|
consistency_max_fragments: int = Field(5, description="Максимальное количество фрагментов для проверки противоречий")
|
||||||
critique: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1))
|
critique: PromptTemperatures = Field(default_factory=lambda: PromptTemperatures(temperature=0.1))
|
||||||
rerank: 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))
|
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="Максимальная глубина рекурсии")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Главная модель конфигурации приложения
|
# Главная модель конфигурации приложения
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -179,7 +185,6 @@ class AppConfig(BaseModel):
|
|||||||
"""
|
"""
|
||||||
name: str = Field(..., description="Имя профиля бота")
|
name: str = Field(..., description="Имя профиля бота")
|
||||||
jid: str = Field(..., description="JID бота (user@domain)")
|
jid: str = Field(..., description="JID бота (user@domain)")
|
||||||
password_env: str = Field(..., description="Имя переменной окружения с паролем XMPP")
|
|
||||||
resource: str = Field("bot", description="Ресурс XMPP")
|
resource: str = Field("bot", description="Ресурс XMPP")
|
||||||
status_message: str = Field(..., description="Сообщение статуса присутствия")
|
status_message: str = Field(..., description="Сообщение статуса присутствия")
|
||||||
xmpp_server: Optional[str] = Field(None, description="XMPP сервер (если не указан, берётся из JID)")
|
xmpp_server: Optional[str] = Field(None, description="XMPP сервер (если не указан, берётся из JID)")
|
||||||
@@ -188,6 +193,8 @@ class AppConfig(BaseModel):
|
|||||||
admin_jids: List[str] = Field([], description="Список JID администраторов")
|
admin_jids: List[str] = Field([], description="Список JID администраторов")
|
||||||
log_level: str = Field("INFO", description="Уровень логирования")
|
log_level: str = Field("INFO", description="Уровень логирования")
|
||||||
rag_server_url: str = Field("http://localhost:8080", description="URL RAG-сервера")
|
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
|
database: DatabaseConfig
|
||||||
@@ -205,14 +212,9 @@ class AppConfig(BaseModel):
|
|||||||
file_permissions: FilePermissionsConfig
|
file_permissions: FilePermissionsConfig
|
||||||
ai: AIConfig
|
ai: AIConfig
|
||||||
prompts: PromptsConfig
|
prompts: PromptsConfig
|
||||||
|
web_scraper: WebScraperConfig = Field(default_factory=WebScraperConfig)
|
||||||
|
|
||||||
# Дополнительные вычисляемые поля (заполняются при загрузке)
|
# Дополнительные поля для путей (заполняются при загрузке)
|
||||||
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="Корень профиля")
|
profile_dir: Optional[Path] = Field(None, description="Корень профиля")
|
||||||
data_dir_path: Optional[Path] = Field(None)
|
data_dir_path: Optional[Path] = Field(None)
|
||||||
temp_dir_path: Optional[Path] = Field(None)
|
temp_dir_path: Optional[Path] = Field(None)
|
||||||
@@ -224,5 +226,306 @@ class AppConfig(BaseModel):
|
|||||||
# Промпты (загружаются из файлов)
|
# Промпты (загружаются из файлов)
|
||||||
prompts_content: Optional[Dict[str, str]] = Field(default_factory=dict, description="Содержимое промптов")
|
prompts_content: Optional[Dict[str, str]] = Field(default_factory=dict, description="Содержимое промптов")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Прокси-свойства для обратной совместимости со старым BotConfig
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# База данных
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Qdrant
|
||||||
|
@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
|
||||||
|
|
||||||
|
# GigaChat
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Embedding
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Chunking
|
||||||
|
@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
|
||||||
|
|
||||||
|
# RAG
|
||||||
|
@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
|
||||||
|
|
||||||
|
# HTTP
|
||||||
|
@property
|
||||||
|
def http_download_timeout(self) -> int:
|
||||||
|
return self.http.download_timeout
|
||||||
|
|
||||||
|
@property
|
||||||
|
def http_upload_timeout(self) -> int:
|
||||||
|
return self.http.upload_timeout
|
||||||
|
|
||||||
|
# Features
|
||||||
|
@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
|
||||||
|
|
||||||
|
# AI
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Web scraper
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Prompts temperatures
|
||||||
|
@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
|
||||||
|
|
||||||
|
# File permissions
|
||||||
|
@property
|
||||||
|
def file_mode(self) -> int:
|
||||||
|
return int(self.file_permissions.mode, 8)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "forbid" # Запрещаем неизвестные поля
|
extra = "forbid" # Запрещаем неизвестные поля
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Dict, List, Any
|
from typing import Optional, Dict, List, Any
|
||||||
|
|
||||||
from rag.rag_orchestrator import RAGOrchestrator
|
from .rag_orchestrator import RAGOrchestrator
|
||||||
from rag.services.kb_service import KBService
|
from .services.kb_service import KBService
|
||||||
from rag.services.giga_client import GigaClient
|
from .services.giga_client import GigaClient
|
||||||
from rag.services.file_service import FileService
|
from .services.file_service import FileService
|
||||||
from rag.services.postgres_service import PostgresService
|
from .services.postgres_service import PostgresService
|
||||||
from rag.config_models import AppConfig
|
from .config_models import AppConfig
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Dict, List, Any
|
from typing import Optional, Dict, List, Any
|
||||||
|
|
||||||
from rag.services.postgres_service import PostgresService
|
from .services.postgres_service import PostgresService
|
||||||
from rag.services.qdrant_service import QdrantService
|
from .services.qdrant_service import QdrantService
|
||||||
from rag.services.embedding_service import EmbeddingService
|
from .services.embedding_service import EmbeddingService
|
||||||
from rag.services.kb_service import KBService
|
from .services.kb_service import KBService
|
||||||
from rag.services.giga_client import GigaClient
|
from .services.giga_client import GigaClient
|
||||||
from rag.services.file_service import FileService
|
from .services.file_service import FileService
|
||||||
from rag.history_manager import HistoryManager
|
from .history_manager import HistoryManager
|
||||||
from rag.intent_router import IntentRouter
|
from .intent_router import IntentRouter
|
||||||
from rag.query_processor import QueryProcessor
|
from .query_processor import QueryProcessor
|
||||||
from rag.indexing_manager import IndexingManager
|
from .indexing_manager import IndexingManager
|
||||||
from rag.functions.intent_classify import classify_intent
|
from .functions.intent_classify import classify_intent
|
||||||
from rag.utils.text_utils import count_tokens
|
from .utils.text_utils import count_tokens
|
||||||
from rag.config_models import AppConfig
|
from .config_models import AppConfig
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -183,9 +183,9 @@ class RAGOrchestrator:
|
|||||||
system_prompt = prompts.get('system', None)
|
system_prompt = prompts.get('system', None)
|
||||||
|
|
||||||
# 2. Расчёт лимитов токенов
|
# 2. Расчёт лимитов токенов
|
||||||
max_model_tokens = self.config.rag.max_model_tokens
|
max_model_tokens = self.config.max_model_tokens
|
||||||
reserved_for_answer = self.config.rag.reserved_for_answer_tokens
|
reserved_for_answer = self.config.reserved_for_answer_tokens
|
||||||
reserved_for_overhead = self.config.rag.reserved_for_overhead_tokens
|
reserved_for_overhead = self.config.reserved_for_overhead_tokens
|
||||||
|
|
||||||
token_info = self._prepare_prompt_parts(
|
token_info = self._prepare_prompt_parts(
|
||||||
synthesis_template=synthesis_template,
|
synthesis_template=synthesis_template,
|
||||||
@@ -211,7 +211,7 @@ class RAGOrchestrator:
|
|||||||
|
|
||||||
# 5. Классификация намерений
|
# 5. Классификация намерений
|
||||||
intent = intent_override
|
intent = intent_override
|
||||||
if intent is None and self.config.features.enable_intent_classification:
|
if intent is None and self.config.enable_intent_classification:
|
||||||
intent_prompt = prompts.get('intent', '')
|
intent_prompt = prompts.get('intent', '')
|
||||||
if intent_prompt:
|
if intent_prompt:
|
||||||
intent = await classify_intent(
|
intent = await classify_intent(
|
||||||
@@ -226,7 +226,7 @@ class RAGOrchestrator:
|
|||||||
intent = intent or "GENERAL"
|
intent = intent or "GENERAL"
|
||||||
|
|
||||||
# 6. Принудительная установка SURGICAL по ключевым словам
|
# 6. Принудительная установка SURGICAL по ключевым словам
|
||||||
keywords = self.config.features.surgical_keywords
|
keywords = self.config.surgical_keywords
|
||||||
if any(kw in query.lower() for kw in keywords) and last_file_path:
|
if any(kw in query.lower() for kw in keywords) and last_file_path:
|
||||||
intent = "SURGICAL"
|
intent = "SURGICAL"
|
||||||
logger.info(f"Принудительный Intent: SURGICAL (есть файл и ключевые слова)")
|
logger.info(f"Принудительный Intent: SURGICAL (есть файл и ключевые слова)")
|
||||||
@@ -255,7 +255,6 @@ class RAGOrchestrator:
|
|||||||
sources = router_result.get("sources", [])
|
sources = router_result.get("sources", [])
|
||||||
else:
|
else:
|
||||||
# Обычный RAG-пайплайн (GENERAL, FACT, PROCEDURE, COMPARISON, CALCULATION)
|
# Обычный RAG-пайплайн (GENERAL, FACT, PROCEDURE, COMPARISON, CALCULATION)
|
||||||
# Вычисляем, сколько токенов осталось для контекста после истории
|
|
||||||
history_tokens = sum(count_tokens(msg['content']) for msg in formatted_history)
|
history_tokens = sum(count_tokens(msg['content']) for msg in formatted_history)
|
||||||
available_for_context = available_for_history_and_context - history_tokens
|
available_for_context = available_for_history_and_context - history_tokens
|
||||||
available_for_context = max(available_for_context, 0)
|
available_for_context = max(available_for_context, 0)
|
||||||
|
|||||||
@@ -3,16 +3,12 @@
|
|||||||
"""
|
"""
|
||||||
RAG-сервер – отдельный HTTP-сервис для обработки RAG-запросов.
|
RAG-сервер – отдельный HTTP-сервис для обработки RAG-запросов.
|
||||||
Предоставляет API для выполнения RAG-запросов и индексации документов.
|
Предоставляет API для выполнения RAG-запросов и индексации документов.
|
||||||
|
|
||||||
Этот сервер запускается как отдельный процесс и может обслуживать
|
|
||||||
несколько ботов и других систем одновременно.
|
|
||||||
|
|
||||||
ИСПОЛЬЗУЕТ: FastAPI для создания REST API и Uvicorn для запуска.
|
|
||||||
ИСТОРИЯ ХРАНИТСЯ НА СЕРВЕРЕ (в PostgreSQL).
|
ИСТОРИЯ ХРАНИТСЯ НА СЕРВЕРЕ (в PostgreSQL).
|
||||||
|
|
||||||
ДОБАВЛЕНО:
|
ДОБАВЛЕНО:
|
||||||
- Эндпоинт /rag/vision для распознавания текста на изображениях (OCR)
|
- Эндпоинт /rag/vision для распознавания текста на изображениях (OCR)
|
||||||
- Эндпоинт /rag/transcribe для транскрибации аудио (SaluteSpeech)
|
- Эндпоинт /rag/transcribe для транскрибации аудио (SaluteSpeech)
|
||||||
|
- Использование Pydantic-модели AppConfig для конфигурации.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -32,16 +28,16 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
# Импортируем наши модули (теперь из пакета rag)
|
# Импорты наших модулей
|
||||||
from rag.utils.config_loader import load_config
|
from .utils.config_loader import load_config
|
||||||
from rag.utils.logger import setup_logging
|
from .utils.logger import setup_logging
|
||||||
from rag.services.postgres_service import PostgresService
|
from .services.postgres_service import PostgresService
|
||||||
from rag.services.qdrant_service import QdrantService
|
from .services.qdrant_service import QdrantService
|
||||||
from rag.services.embedding_service import EmbeddingService
|
from .services.embedding_service import EmbeddingService
|
||||||
from rag.services.kb_service import KBService
|
from .services.kb_service import KBService
|
||||||
from rag.services.giga_client import GigaClient
|
from .services.giga_client import GigaClient
|
||||||
from rag.services.file_service import FileService
|
from .services.file_service import FileService
|
||||||
from rag.rag_orchestrator import RAGOrchestrator
|
from .rag_orchestrator import RAGOrchestrator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -114,7 +110,7 @@ class TranscribeResponse(BaseModel):
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
_orchestrator: Optional[RAGOrchestrator] = None
|
_orchestrator: Optional[RAGOrchestrator] = None
|
||||||
_config: Optional[AppConfig] = None
|
_config = None
|
||||||
|
|
||||||
|
|
||||||
def get_orchestrator() -> RAGOrchestrator:
|
def get_orchestrator() -> RAGOrchestrator:
|
||||||
@@ -125,7 +121,7 @@ def get_orchestrator() -> RAGOrchestrator:
|
|||||||
return _orchestrator
|
return _orchestrator
|
||||||
|
|
||||||
|
|
||||||
def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
def init_orchestrator(config):
|
||||||
"""Инициализирует все сервисы и создаёт RAGOrchestrator."""
|
"""Инициализирует все сервисы и создаёт RAGOrchestrator."""
|
||||||
global _orchestrator, _config
|
global _orchestrator, _config
|
||||||
_config = config
|
_config = config
|
||||||
@@ -134,31 +130,31 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
|||||||
# ---- 1. Инициализация сервисов ----
|
# ---- 1. Инициализация сервисов ----
|
||||||
# PostgreSQL (история, документы)
|
# PostgreSQL (история, документы)
|
||||||
db = PostgresService(
|
db = PostgresService(
|
||||||
host=config.database.host,
|
host=config.db_host,
|
||||||
port=config.database.port,
|
port=config.db_port,
|
||||||
user=config.database.user,
|
user=config.db_user,
|
||||||
password=config.db_password,
|
password=config.db_password,
|
||||||
db_name=config.database.database
|
db_name=config.db_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Qdrant (векторный поиск)
|
# Qdrant (векторный поиск)
|
||||||
qdrant = QdrantService(
|
qdrant = QdrantService(
|
||||||
host=config.qdrant.host,
|
host=config.qdrant_host,
|
||||||
port=config.qdrant.port,
|
port=config.qdrant_port,
|
||||||
grpc_port=config.qdrant.grpc_port,
|
grpc_port=config.qdrant_grpc_port,
|
||||||
collection_name=config.qdrant.collection,
|
collection_name=config.qdrant_collection,
|
||||||
vector_size=config.qdrant.vector_size,
|
vector_size=config.qdrant_vector_size,
|
||||||
distance=config.qdrant.distance,
|
distance=config.qdrant_distance,
|
||||||
prefer_grpc=False
|
prefer_grpc=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Эмбеддинги (GigaChat)
|
# Эмбеддинги (GigaChat)
|
||||||
embedding = EmbeddingService(
|
embedding = EmbeddingService(
|
||||||
api_key=config.gigachat_api_key,
|
api_key=config.gigachat_api_key,
|
||||||
model=config.embedding.model,
|
model=config.embedding_model,
|
||||||
timeout=config.embedding.timeout,
|
timeout=config.embedding_timeout,
|
||||||
cache_size=config.embedding.cache_size,
|
cache_size=config.embedding_cache_size,
|
||||||
verify_ssl=config.embedding.verify_ssl_certs
|
verify_ssl=config.embedding_verify_ssl
|
||||||
)
|
)
|
||||||
|
|
||||||
# База знаний (индексация, поиск)
|
# База знаний (индексация, поиск)
|
||||||
@@ -166,19 +162,19 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
|||||||
db=db,
|
db=db,
|
||||||
qdrant=qdrant,
|
qdrant=qdrant,
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
collection_name=config.qdrant.collection,
|
collection_name=config.qdrant_collection,
|
||||||
chunk_size_tokens=config.chunking.chunk_size_tokens,
|
chunk_size_tokens=config.chunk_size_tokens,
|
||||||
overlap_tokens=config.chunking.overlap_tokens,
|
overlap_tokens=config.overlap_tokens,
|
||||||
approx_chunk_chars=config.chunking.approx_chunk_chars,
|
approx_chunk_chars=config.chunking_approx_chunk_chars,
|
||||||
approx_overlap_chars=config.chunking.approx_overlap_chars
|
approx_overlap_chars=config.chunking_approx_overlap_chars
|
||||||
)
|
)
|
||||||
|
|
||||||
# GigaChat клиент (генерация)
|
# GigaChat клиент (генерация)
|
||||||
giga = GigaClient(
|
giga = GigaClient(
|
||||||
api_key=config.gigachat_api_key,
|
api_key=config.gigachat_api_key,
|
||||||
model=config.gigachat.model_generation,
|
model=config.ai_model,
|
||||||
temperature=config.gigachat.temperature,
|
temperature=config.ai_temperature,
|
||||||
timeout=config.gigachat.timeout,
|
timeout=config.ai_timeout,
|
||||||
verify_ssl=False
|
verify_ssl=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -186,11 +182,15 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
|||||||
files = FileService(config)
|
files = FileService(config)
|
||||||
|
|
||||||
# ---- 2. Создание оркестратора ----
|
# ---- 2. Создание оркестратора ----
|
||||||
# Загружаем дефолтные промпты (из config.prompts_content)
|
# Загружаем дефолтные промпты (опционально)
|
||||||
default_prompts = config.prompts_content or {}
|
default_prompts = {}
|
||||||
system_prompt = default_prompts.get('system')
|
system_prompt_path = getattr(config, 'system_prompt_file', None)
|
||||||
if system_prompt:
|
if system_prompt_path and system_prompt_path.exists():
|
||||||
default_prompts['system'] = system_prompt
|
try:
|
||||||
|
with open(system_prompt_path, 'r', encoding='utf-8') as f:
|
||||||
|
default_prompts['system'] = f.read()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось загрузить системный промпт: {e}")
|
||||||
|
|
||||||
orchestrator = RAGOrchestrator(
|
orchestrator = RAGOrchestrator(
|
||||||
db=db,
|
db=db,
|
||||||
@@ -215,7 +215,7 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="RAG-сервер",
|
title="RAG-сервер",
|
||||||
description="Единый сервис для RAG-обработки запросов и индексации документов",
|
description="Единый сервис для RAG-обработки запросов и индексации документов",
|
||||||
version="1.0.0"
|
version="3.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Разрешаем CORS для возможности запросов с других доменов (для тестов)
|
# Разрешаем CORS для возможности запросов с других доменов (для тестов)
|
||||||
@@ -436,7 +436,7 @@ def main():
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Загружаем конфигурацию с помощью новой функции load_config
|
# Загружаем конфигурацию через Pydantic
|
||||||
try:
|
try:
|
||||||
config = load_config(args.profile_dir)
|
config = load_config(args.profile_dir)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -445,9 +445,7 @@ def main():
|
|||||||
|
|
||||||
# Настраиваем логирование
|
# Настраиваем логирование
|
||||||
log_level = getattr(logging, args.log_level.upper(), logging.INFO)
|
log_level = getattr(logging, args.log_level.upper(), logging.INFO)
|
||||||
# Передаём путь к логу из конфига
|
setup_logging(config.name, config.log_file_path, log_level=log_level)
|
||||||
log_file_path = config.log_file_path or Path("/tmp/rag_server.log")
|
|
||||||
setup_logging(config.name, log_file_path, log_level=log_level)
|
|
||||||
|
|
||||||
logger.info(f"Запуск RAG-сервера с профилем {args.profile_dir}")
|
logger.info(f"Запуск RAG-сервера с профилем {args.profile_dir}")
|
||||||
logger.info(f"Хост: {args.host}, порт: {args.port}")
|
logger.info(f"Хост: {args.host}, порт: {args.port}")
|
||||||
@@ -461,7 +459,7 @@ def main():
|
|||||||
|
|
||||||
# Запускаем Uvicorn
|
# Запускаем Uvicorn
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"rag.rag_server:app", # теперь модуль из пакета rag
|
"rag.rag_server:app",
|
||||||
host=args.host,
|
host=args.host,
|
||||||
port=args.port,
|
port=args.port,
|
||||||
log_level=args.log_level,
|
log_level=args.log_level,
|
||||||
|
|||||||
@@ -9,14 +9,11 @@ import os
|
|||||||
import yaml
|
import yaml
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from .config_models import AppConfig, DatabaseConfig, QdrantConfig, GigaChatConfig, \
|
from ..config_models import AppConfig
|
||||||
EmbeddingConfig, ChunkingConfig, RAGConfig, HTTPServerConfig, HTTPConfig, \
|
|
||||||
SummarizationConfig, PathsConfig, FeaturesConfig, CleanupConfig, FilePermissionsConfig, \
|
|
||||||
AIConfig, PromptsConfig, PromptTemperatures
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -66,16 +63,13 @@ def load_config(profile_dir: str) -> AppConfig:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Подставляем пароли и ключи в секции
|
# Подставляем пароли и ключи в секции
|
||||||
# В database: password_env -> фактический пароль
|
|
||||||
if 'database' in merged and 'password_env' in merged['database']:
|
if 'database' in merged and 'password_env' in merged['database']:
|
||||||
env_name = merged['database']['password_env']
|
env_name = merged['database']['password_env']
|
||||||
merged['database']['password'] = env_vars.get(env_name)
|
merged['database']['password'] = env_vars.get(env_name)
|
||||||
if not merged['database']['password']:
|
if not merged['database']['password']:
|
||||||
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
||||||
# удаляем password_env, чтобы не передавать в модель
|
|
||||||
del merged['database']['password_env']
|
del merged['database']['password_env']
|
||||||
|
|
||||||
# Аналогично для gigachat
|
|
||||||
if 'gigachat' in merged and 'credentials_env' in merged['gigachat']:
|
if 'gigachat' in merged and 'credentials_env' in merged['gigachat']:
|
||||||
env_name = merged['gigachat']['credentials_env']
|
env_name = merged['gigachat']['credentials_env']
|
||||||
merged['gigachat']['api_key'] = env_vars.get(env_name)
|
merged['gigachat']['api_key'] = env_vars.get(env_name)
|
||||||
@@ -83,7 +77,6 @@ def load_config(profile_dir: str) -> AppConfig:
|
|||||||
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
||||||
del merged['gigachat']['credentials_env']
|
del merged['gigachat']['credentials_env']
|
||||||
|
|
||||||
# Для XMPP пароля
|
|
||||||
if 'password_env' in merged:
|
if 'password_env' in merged:
|
||||||
env_name = merged['password_env']
|
env_name = merged['password_env']
|
||||||
merged['xmpp_password'] = env_vars.get(env_name)
|
merged['xmpp_password'] = env_vars.get(env_name)
|
||||||
@@ -91,30 +84,30 @@ def load_config(profile_dir: str) -> AppConfig:
|
|||||||
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
||||||
del merged['password_env']
|
del merged['password_env']
|
||||||
|
|
||||||
# Для SaluteSpeech (если есть в секции features или отдельно)
|
|
||||||
# У нас он может быть в .env, но в модели нет отдельного поля; добавим в итоговый объект
|
|
||||||
merged['salute_speech_auth'] = env_vars.get('SALUTE_SPEECH_AUTH')
|
merged['salute_speech_auth'] = env_vars.get('SALUTE_SPEECH_AUTH')
|
||||||
|
|
||||||
# Преобразуем пути в абсолютные с учётом profile_dir
|
# Преобразуем пути в абсолютные с учётом profile_dir
|
||||||
paths_section = merged.get('paths', {})
|
paths_section = merged.get('paths', {})
|
||||||
# Если пути относительные, дополняем их относительно profile_dir
|
|
||||||
for key in ['data_dir', 'temp_dir', 'prompts_dir', 'upload_dir', 'log_dir']:
|
for key in ['data_dir', 'temp_dir', 'prompts_dir', 'upload_dir', 'log_dir']:
|
||||||
if key in paths_section:
|
if key in paths_section:
|
||||||
p = Path(paths_section[key])
|
p = Path(paths_section[key])
|
||||||
if not p.is_absolute():
|
if not p.is_absolute():
|
||||||
paths_section[key] = str(profile_path / p)
|
paths_section[key] = str(profile_path / p)
|
||||||
# log_filename оставляем как есть
|
|
||||||
|
|
||||||
# Добавляем profile_dir в merged для передачи в модель
|
# Добавляем profile_dir
|
||||||
merged['profile_dir'] = str(profile_path)
|
merged['profile_dir'] = str(profile_path)
|
||||||
|
|
||||||
# Создаём экземпляр AppConfig (Pydantic выполнит валидацию)
|
# Добавляем web_scraper (если отсутствует)
|
||||||
|
if 'web_scraper' not in merged:
|
||||||
|
merged['web_scraper'] = {'max_pages': 5, 'max_depth': 1}
|
||||||
|
|
||||||
|
# Создаём экземпляр AppConfig
|
||||||
try:
|
try:
|
||||||
config = AppConfig(**merged)
|
config = AppConfig(**merged)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Ошибка валидации конфигурации: {e}")
|
raise ValueError(f"Ошибка валидации конфигурации: {e}")
|
||||||
|
|
||||||
# Дополнительно загружаем промпты из файлов
|
# Загружаем промпты из файлов
|
||||||
prompts_dir = config.prompts_dir_path
|
prompts_dir = config.prompts_dir_path
|
||||||
if prompts_dir and prompts_dir.exists():
|
if prompts_dir and prompts_dir.exists():
|
||||||
prompts_content = {}
|
prompts_content = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user