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.
|
||||
Используются для загрузки и валидации настроек из YAML-файлов (rag.conf и bot.conf).
|
||||
Обеспечивают строгую типизацию и автоматическую проверку обязательных полей.
|
||||
|
||||
Добавлены прокси-свойства для обратной совместимости с BotConfig.
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Dict, Any
|
||||
@@ -18,7 +20,6 @@ class DatabaseConfig(BaseModel):
|
||||
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)")
|
||||
@@ -42,7 +43,7 @@ class QdrantConfig(BaseModel):
|
||||
|
||||
class GigaChatConfig(BaseModel):
|
||||
"""Настройки GigaChat API."""
|
||||
credentials_env: str = Field(..., description="Имя переменной окружения с 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="Таймаут запросов в секундах")
|
||||
@@ -164,10 +165,15 @@ class PromptsConfig(BaseModel):
|
||||
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 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="Имя профиля бота")
|
||||
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)")
|
||||
@@ -188,6 +193,8 @@ class AppConfig(BaseModel):
|
||||
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
|
||||
@@ -205,14 +212,9 @@ class AppConfig(BaseModel):
|
||||
file_permissions: FilePermissionsConfig
|
||||
ai: AIConfig
|
||||
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="Корень профиля")
|
||||
data_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="Содержимое промптов")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Прокси-свойства для обратной совместимости со старым 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:
|
||||
extra = "forbid" # Запрещаем неизвестные поля
|
||||
@@ -16,12 +16,12 @@
|
||||
import logging
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
from rag.rag_orchestrator import RAGOrchestrator
|
||||
from rag.services.kb_service import KBService
|
||||
from rag.services.giga_client import GigaClient
|
||||
from rag.services.file_service import FileService
|
||||
from rag.services.postgres_service import PostgresService
|
||||
from rag.config_models import AppConfig
|
||||
from .rag_orchestrator import RAGOrchestrator
|
||||
from .services.kb_service import KBService
|
||||
from .services.giga_client import GigaClient
|
||||
from .services.file_service import FileService
|
||||
from .services.postgres_service import PostgresService
|
||||
from .config_models import AppConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
import logging
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
from rag.services.postgres_service import PostgresService
|
||||
from rag.services.qdrant_service import QdrantService
|
||||
from rag.services.embedding_service import EmbeddingService
|
||||
from rag.services.kb_service import KBService
|
||||
from rag.services.giga_client import GigaClient
|
||||
from rag.services.file_service import FileService
|
||||
from rag.history_manager import HistoryManager
|
||||
from rag.intent_router import IntentRouter
|
||||
from rag.query_processor import QueryProcessor
|
||||
from rag.indexing_manager import IndexingManager
|
||||
from rag.functions.intent_classify import classify_intent
|
||||
from rag.utils.text_utils import count_tokens
|
||||
from rag.config_models import AppConfig
|
||||
from .services.postgres_service import PostgresService
|
||||
from .services.qdrant_service import QdrantService
|
||||
from .services.embedding_service import EmbeddingService
|
||||
from .services.kb_service import KBService
|
||||
from .services.giga_client import GigaClient
|
||||
from .services.file_service import FileService
|
||||
from .history_manager import HistoryManager
|
||||
from .intent_router import IntentRouter
|
||||
from .query_processor import QueryProcessor
|
||||
from .indexing_manager import IndexingManager
|
||||
from .functions.intent_classify import classify_intent
|
||||
from .utils.text_utils import count_tokens
|
||||
from .config_models import AppConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -183,9 +183,9 @@ class RAGOrchestrator:
|
||||
system_prompt = prompts.get('system', None)
|
||||
|
||||
# 2. Расчёт лимитов токенов
|
||||
max_model_tokens = self.config.rag.max_model_tokens
|
||||
reserved_for_answer = self.config.rag.reserved_for_answer_tokens
|
||||
reserved_for_overhead = self.config.rag.reserved_for_overhead_tokens
|
||||
max_model_tokens = self.config.max_model_tokens
|
||||
reserved_for_answer = self.config.reserved_for_answer_tokens
|
||||
reserved_for_overhead = self.config.reserved_for_overhead_tokens
|
||||
|
||||
token_info = self._prepare_prompt_parts(
|
||||
synthesis_template=synthesis_template,
|
||||
@@ -211,7 +211,7 @@ class RAGOrchestrator:
|
||||
|
||||
# 5. Классификация намерений
|
||||
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', '')
|
||||
if intent_prompt:
|
||||
intent = await classify_intent(
|
||||
@@ -226,7 +226,7 @@ class RAGOrchestrator:
|
||||
intent = intent or "GENERAL"
|
||||
|
||||
# 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:
|
||||
intent = "SURGICAL"
|
||||
logger.info(f"Принудительный Intent: SURGICAL (есть файл и ключевые слова)")
|
||||
@@ -255,7 +255,6 @@ class RAGOrchestrator:
|
||||
sources = router_result.get("sources", [])
|
||||
else:
|
||||
# Обычный RAG-пайплайн (GENERAL, FACT, PROCEDURE, COMPARISON, CALCULATION)
|
||||
# Вычисляем, сколько токенов осталось для контекста после истории
|
||||
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 = max(available_for_context, 0)
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
"""
|
||||
RAG-сервер – отдельный HTTP-сервис для обработки RAG-запросов.
|
||||
Предоставляет API для выполнения RAG-запросов и индексации документов.
|
||||
|
||||
Этот сервер запускается как отдельный процесс и может обслуживать
|
||||
несколько ботов и других систем одновременно.
|
||||
|
||||
ИСПОЛЬЗУЕТ: FastAPI для создания REST API и Uvicorn для запуска.
|
||||
ИСТОРИЯ ХРАНИТСЯ НА СЕРВЕРЕ (в PostgreSQL).
|
||||
|
||||
ДОБАВЛЕНО:
|
||||
- Эндпоинт /rag/vision для распознавания текста на изображениях (OCR)
|
||||
- Эндпоинт /rag/transcribe для транскрибации аудио (SaluteSpeech)
|
||||
- Использование Pydantic-модели AppConfig для конфигурации.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -32,16 +28,16 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, Field
|
||||
import uvicorn
|
||||
|
||||
# Импортируем наши модули (теперь из пакета rag)
|
||||
from rag.utils.config_loader import load_config
|
||||
from rag.utils.logger import setup_logging
|
||||
from rag.services.postgres_service import PostgresService
|
||||
from rag.services.qdrant_service import QdrantService
|
||||
from rag.services.embedding_service import EmbeddingService
|
||||
from rag.services.kb_service import KBService
|
||||
from rag.services.giga_client import GigaClient
|
||||
from rag.services.file_service import FileService
|
||||
from rag.rag_orchestrator import RAGOrchestrator
|
||||
# Импорты наших модулей
|
||||
from .utils.config_loader import load_config
|
||||
from .utils.logger import setup_logging
|
||||
from .services.postgres_service import PostgresService
|
||||
from .services.qdrant_service import QdrantService
|
||||
from .services.embedding_service import EmbeddingService
|
||||
from .services.kb_service import KBService
|
||||
from .services.giga_client import GigaClient
|
||||
from .services.file_service import FileService
|
||||
from .rag_orchestrator import RAGOrchestrator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -114,7 +110,7 @@ class TranscribeResponse(BaseModel):
|
||||
# ============================================================
|
||||
|
||||
_orchestrator: Optional[RAGOrchestrator] = None
|
||||
_config: Optional[AppConfig] = None
|
||||
_config = None
|
||||
|
||||
|
||||
def get_orchestrator() -> RAGOrchestrator:
|
||||
@@ -125,7 +121,7 @@ def get_orchestrator() -> RAGOrchestrator:
|
||||
return _orchestrator
|
||||
|
||||
|
||||
def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
||||
def init_orchestrator(config):
|
||||
"""Инициализирует все сервисы и создаёт RAGOrchestrator."""
|
||||
global _orchestrator, _config
|
||||
_config = config
|
||||
@@ -134,31 +130,31 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
||||
# ---- 1. Инициализация сервисов ----
|
||||
# PostgreSQL (история, документы)
|
||||
db = PostgresService(
|
||||
host=config.database.host,
|
||||
port=config.database.port,
|
||||
user=config.database.user,
|
||||
host=config.db_host,
|
||||
port=config.db_port,
|
||||
user=config.db_user,
|
||||
password=config.db_password,
|
||||
db_name=config.database.database
|
||||
db_name=config.db_name
|
||||
)
|
||||
|
||||
# Qdrant (векторный поиск)
|
||||
qdrant = QdrantService(
|
||||
host=config.qdrant.host,
|
||||
port=config.qdrant.port,
|
||||
grpc_port=config.qdrant.grpc_port,
|
||||
collection_name=config.qdrant.collection,
|
||||
vector_size=config.qdrant.vector_size,
|
||||
distance=config.qdrant.distance,
|
||||
host=config.qdrant_host,
|
||||
port=config.qdrant_port,
|
||||
grpc_port=config.qdrant_grpc_port,
|
||||
collection_name=config.qdrant_collection,
|
||||
vector_size=config.qdrant_vector_size,
|
||||
distance=config.qdrant_distance,
|
||||
prefer_grpc=False
|
||||
)
|
||||
|
||||
# Эмбеддинги (GigaChat)
|
||||
embedding = EmbeddingService(
|
||||
api_key=config.gigachat_api_key,
|
||||
model=config.embedding.model,
|
||||
timeout=config.embedding.timeout,
|
||||
cache_size=config.embedding.cache_size,
|
||||
verify_ssl=config.embedding.verify_ssl_certs
|
||||
model=config.embedding_model,
|
||||
timeout=config.embedding_timeout,
|
||||
cache_size=config.embedding_cache_size,
|
||||
verify_ssl=config.embedding_verify_ssl
|
||||
)
|
||||
|
||||
# База знаний (индексация, поиск)
|
||||
@@ -166,19 +162,19 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
||||
db=db,
|
||||
qdrant=qdrant,
|
||||
embedding=embedding,
|
||||
collection_name=config.qdrant.collection,
|
||||
chunk_size_tokens=config.chunking.chunk_size_tokens,
|
||||
overlap_tokens=config.chunking.overlap_tokens,
|
||||
approx_chunk_chars=config.chunking.approx_chunk_chars,
|
||||
approx_overlap_chars=config.chunking.approx_overlap_chars
|
||||
collection_name=config.qdrant_collection,
|
||||
chunk_size_tokens=config.chunk_size_tokens,
|
||||
overlap_tokens=config.overlap_tokens,
|
||||
approx_chunk_chars=config.chunking_approx_chunk_chars,
|
||||
approx_overlap_chars=config.chunking_approx_overlap_chars
|
||||
)
|
||||
|
||||
# GigaChat клиент (генерация)
|
||||
giga = GigaClient(
|
||||
api_key=config.gigachat_api_key,
|
||||
model=config.gigachat.model_generation,
|
||||
temperature=config.gigachat.temperature,
|
||||
timeout=config.gigachat.timeout,
|
||||
model=config.ai_model,
|
||||
temperature=config.ai_temperature,
|
||||
timeout=config.ai_timeout,
|
||||
verify_ssl=False
|
||||
)
|
||||
|
||||
@@ -186,11 +182,15 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
||||
files = FileService(config)
|
||||
|
||||
# ---- 2. Создание оркестратора ----
|
||||
# Загружаем дефолтные промпты (из config.prompts_content)
|
||||
default_prompts = config.prompts_content or {}
|
||||
system_prompt = default_prompts.get('system')
|
||||
if system_prompt:
|
||||
default_prompts['system'] = system_prompt
|
||||
# Загружаем дефолтные промпты (опционально)
|
||||
default_prompts = {}
|
||||
system_prompt_path = getattr(config, 'system_prompt_file', None)
|
||||
if system_prompt_path and system_prompt_path.exists():
|
||||
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(
|
||||
db=db,
|
||||
@@ -215,7 +215,7 @@ def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
|
||||
app = FastAPI(
|
||||
title="RAG-сервер",
|
||||
description="Единый сервис для RAG-обработки запросов и индексации документов",
|
||||
version="1.0.0"
|
||||
version="3.0.0"
|
||||
)
|
||||
|
||||
# Разрешаем CORS для возможности запросов с других доменов (для тестов)
|
||||
@@ -436,7 +436,7 @@ def main():
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Загружаем конфигурацию с помощью новой функции load_config
|
||||
# Загружаем конфигурацию через Pydantic
|
||||
try:
|
||||
config = load_config(args.profile_dir)
|
||||
except Exception as e:
|
||||
@@ -445,9 +445,7 @@ def main():
|
||||
|
||||
# Настраиваем логирование
|
||||
log_level = getattr(logging, args.log_level.upper(), logging.INFO)
|
||||
# Передаём путь к логу из конфига
|
||||
log_file_path = config.log_file_path or Path("/tmp/rag_server.log")
|
||||
setup_logging(config.name, log_file_path, log_level=log_level)
|
||||
setup_logging(config.name, config.log_file_path, log_level=log_level)
|
||||
|
||||
logger.info(f"Запуск RAG-сервера с профилем {args.profile_dir}")
|
||||
logger.info(f"Хост: {args.host}, порт: {args.port}")
|
||||
@@ -461,7 +459,7 @@ def main():
|
||||
|
||||
# Запускаем Uvicorn
|
||||
uvicorn.run(
|
||||
"rag.rag_server:app", # теперь модуль из пакета rag
|
||||
"rag.rag_server:app",
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
log_level=args.log_level,
|
||||
|
||||
@@ -9,14 +9,11 @@ import os
|
||||
import yaml
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Dict, Any
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from .config_models import AppConfig, DatabaseConfig, QdrantConfig, GigaChatConfig, \
|
||||
EmbeddingConfig, ChunkingConfig, RAGConfig, HTTPServerConfig, HTTPConfig, \
|
||||
SummarizationConfig, PathsConfig, FeaturesConfig, CleanupConfig, FilePermissionsConfig, \
|
||||
AIConfig, PromptsConfig, PromptTemperatures
|
||||
from ..config_models import AppConfig
|
||||
|
||||
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']:
|
||||
env_name = merged['database']['password_env']
|
||||
merged['database']['password'] = env_vars.get(env_name)
|
||||
if not merged['database']['password']:
|
||||
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
||||
# удаляем password_env, чтобы не передавать в модель
|
||||
del merged['database']['password_env']
|
||||
|
||||
# Аналогично для gigachat
|
||||
if 'gigachat' in merged and 'credentials_env' in merged['gigachat']:
|
||||
env_name = merged['gigachat']['credentials_env']
|
||||
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} не задана или пуста")
|
||||
del merged['gigachat']['credentials_env']
|
||||
|
||||
# Для XMPP пароля
|
||||
if 'password_env' in merged:
|
||||
env_name = merged['password_env']
|
||||
merged['xmpp_password'] = env_vars.get(env_name)
|
||||
@@ -91,30 +84,30 @@ def load_config(profile_dir: str) -> AppConfig:
|
||||
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
|
||||
del merged['password_env']
|
||||
|
||||
# Для SaluteSpeech (если есть в секции features или отдельно)
|
||||
# У нас он может быть в .env, но в модели нет отдельного поля; добавим в итоговый объект
|
||||
merged['salute_speech_auth'] = env_vars.get('SALUTE_SPEECH_AUTH')
|
||||
|
||||
# Преобразуем пути в абсолютные с учётом profile_dir
|
||||
paths_section = merged.get('paths', {})
|
||||
# Если пути относительные, дополняем их относительно profile_dir
|
||||
for key in ['data_dir', 'temp_dir', 'prompts_dir', 'upload_dir', 'log_dir']:
|
||||
if key in paths_section:
|
||||
p = Path(paths_section[key])
|
||||
if not p.is_absolute():
|
||||
paths_section[key] = str(profile_path / p)
|
||||
# log_filename оставляем как есть
|
||||
|
||||
# Добавляем profile_dir в merged для передачи в модель
|
||||
# Добавляем profile_dir
|
||||
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:
|
||||
config = AppConfig(**merged)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Ошибка валидации конфигурации: {e}")
|
||||
|
||||
# Дополнительно загружаем промпты из файлов
|
||||
# Загружаем промпты из файлов
|
||||
prompts_dir = config.prompts_dir_path
|
||||
if prompts_dir and prompts_dir.exists():
|
||||
prompts_content = {}
|
||||
|
||||
Reference in New Issue
Block a user