From a3f52888dbabafc792635ba80b469b872651b73e Mon Sep 17 00:00:00 2001 From: Markov Andrey Date: Tue, 30 Jun 2026 14:04:31 +0000 Subject: [PATCH] Update 6 files - /rag/history_manager.py - /rag/query_processor.py - /rag/rag_orchestrator.py - /rag/rag_api.py - /rag/rag_server.py - /rag/__init__.py --- rag/__init__.py | 8 +--- rag/history_manager.py | 29 ++++++------ rag/query_processor.py | 32 ++++++++------ rag/rag_api.py | 15 ++++--- rag/rag_orchestrator.py | 39 ++++++++-------- rag/rag_server.py | 98 +++++++++++++++++++---------------------- 6 files changed, 107 insertions(+), 114 deletions(-) diff --git a/rag/__init__.py b/rag/__init__.py index 14564b8..803fd2f 100644 --- a/rag/__init__.py +++ b/rag/__init__.py @@ -1,16 +1,12 @@ # -*- coding: utf-8 -*- """ -Пакет core – общий код для всех компонентов платформы Эфцекабот. +Пакет rag – RAG-ядро платформы Эфцекабот. Содержит: - RAG-сервер и оркестратор, -- HTTP-клиент для ботов, - сервисы (PostgreSQL, Qdrant, GigaChat), - функции (классификация, суммаризация, проверка орфографии и др.), -- обработчики XMPP-событий, -- команды ботов, -- фоновые воркеры, - утилиты. -Используется как RAG-ядром, так и тонкими XMPP-клиентами-профилями. +Используется как HTTP-сервис. """ __version__ = "3.0.0" diff --git a/rag/history_manager.py b/rag/history_manager.py index c74e901..669b356 100644 --- a/rag/history_manager.py +++ b/rag/history_manager.py @@ -13,10 +13,11 @@ import logging from typing import List, Dict, Optional, Any -from core.services.postgres_service import PostgresService -from core.services.giga_client import GigaClient -from core.utils.text_utils import count_tokens -from core.functions.hierarchical_summarize import hierarchical_summarize +from rag.services.postgres_service import PostgresService +from rag.services.giga_client import GigaClient +from rag.utils.text_utils import count_tokens +from rag.functions.hierarchical_summarize import hierarchical_summarize +from rag.config_models import AppConfig logger = logging.getLogger(__name__) @@ -30,7 +31,7 @@ class HistoryManager: self, db: PostgresService, giga: GigaClient, - config, + config: AppConfig, default_prompts: Optional[Dict[str, str]] = None ): """ @@ -39,7 +40,7 @@ class HistoryManager: Аргументы: db: сервис PostgreSQL (для получения и сохранения истории) giga: клиент GigaChat (для сжатия истории) - config: объект конфигурации (BotConfig) + config: объект конфигурации (AppConfig) default_prompts: словарь промптов по умолчанию (нужен для hierarchical_summary) """ self.db = db @@ -47,10 +48,6 @@ class HistoryManager: self.config = config self.default_prompts = default_prompts or {} - # ------------------------------------------------------------------ - # Получение и форматирование истории - # ------------------------------------------------------------------ - async def get_history( self, user_jid: str, @@ -116,7 +113,7 @@ class HistoryManager: if not prompt_template: # Пытаемся загрузить из файла, если он есть try: - prompt_path = self.config.prompts_dir / 'hierarchical_summary.txt' + prompt_path = self.config.prompts_dir_path / 'hierarchical_summary.txt' with open(prompt_path, 'r', encoding='utf-8') as f: prompt_template = f.read() except Exception as e: @@ -130,11 +127,11 @@ class HistoryManager: ) # Параметры сжатия из конфига - summarization_config = getattr(self.config, 'summarization', {}) - target_tokens = summarization_config.get('target_tokens_after_summary', max_tokens) - chunk_size = summarization_config.get('chunk_size_tokens', 500) - max_depth = summarization_config.get('max_depth', 2) - temperature = getattr(self.config, 'ai_temperature', 0.1) + summarization_config = self.config.summarization + target_tokens = summarization_config.target_tokens_after_summary + chunk_size = summarization_config.chunk_size_tokens + max_depth = summarization_config.max_depth + temperature = self.config.ai.temperature try: compressed_text = await hierarchical_summarize( diff --git a/rag/query_processor.py b/rag/query_processor.py index 3f541f3..a1f327b 100644 --- a/rag/query_processor.py +++ b/rag/query_processor.py @@ -9,12 +9,13 @@ import logging import re from typing import Optional, Dict, List, Any -from core.services.giga_client import GigaClient -from core.services.kb_service import KBService -from core.functions.expand_query import expand_query -from core.functions.rerank_context import rerank_context -from core.functions.critique_answer import critique_answer -from core.utils.text_utils import count_tokens +from rag.services.giga_client import GigaClient +from rag.services.kb_service import KBService +from rag.functions.expand_query import expand_query +from rag.functions.rerank_context import rerank_context +from rag.functions.critique_answer import critique_answer +from rag.utils.text_utils import count_tokens +from rag.config_models import AppConfig logger = logging.getLogger(__name__) @@ -28,7 +29,7 @@ class QueryProcessor: self, giga: GigaClient, kb: KBService, - config, + config: AppConfig, default_prompts: Optional[Dict[str, str]] = None ): """ @@ -37,7 +38,7 @@ class QueryProcessor: Аргументы: giga: клиент GigaChat kb: сервис базы знаний - config: объект конфигурации + config: объект конфигурации (AppConfig) default_prompts: словарь промптов по умолчанию """ self.giga = giga @@ -73,18 +74,20 @@ class QueryProcessor: Словарь с ключами 'answer', 'context', 'sources', 'confidence' """ # 1. Расширение запроса + expand_prompt = prompts.get('expand', '') expanded = await expand_query( giga=self.giga, query=query, - prompt_text=prompts.get('expand', ''), + prompt_text=expand_prompt, bot_config=self.config ) search_query = expanded if expanded and expanded != query else query # 2. Поиск релевантного контекста в базе знаний + top_k = self.config.rag.default_top_k context = await self.kb.find_relevant_info( search_query, user_jid, room_jid, - top_k=getattr(self.config, 'rag_default_top_k', 30) + top_k=top_k ) logger.info(f"Найден контекст длиной {len(context)} символов (room={room_jid})") @@ -103,7 +106,7 @@ class QueryProcessor: context = "" # 4. Переранжирование контекста (если включено и контекст достаточно длинный) - rerank_min_length = getattr(self.config, 'rerank_min_length', 5000) + rerank_min_length = self.config.rag.rerank_min_length if intent != "FACT" and len(context) > rerank_min_length: context = await rerank_context( bot=None, @@ -130,16 +133,17 @@ class QueryProcessor: logger.debug(f"Полный запрос к GigaChat (первые 500 символов): {full_query[:500]}") # 6. Генерация ответа + temperature = self.config.ai.temperature answer = await self.giga.chat( history=history, query=full_query, system_prompt=system_prompt, file_id=None, - temperature=getattr(self.config, 'ai_temperature', 0.1) + temperature=temperature ) # 7. Самокритика (если включена) - if getattr(self.config, 'enable_self_critique', False) and context: + if self.config.features.enable_self_critique and context: critique_prompt = prompts.get('critique', '') if critique_prompt: logger.debug("Запуск самокритики") @@ -159,7 +163,7 @@ class QueryProcessor: query=full_query_retry, system_prompt=system_prompt, file_id=None, - temperature=getattr(self.config, 'ai_temperature', 0.1) + temperature=temperature ) # Повторная проверка после перегенерации if not await critique_answer( diff --git a/rag/rag_api.py b/rag/rag_api.py index db4a1bd..0b3d7b6 100644 --- a/rag/rag_api.py +++ b/rag/rag_api.py @@ -16,11 +16,12 @@ import logging from typing import Optional, Dict, List, Any -from core.rag_orchestrator import RAGOrchestrator -from core.services.kb_service import KBService -from core.services.giga_client import GigaClient -from core.services.file_service import FileService -from core.services.postgres_service import PostgresService +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 logger = logging.getLogger(__name__) @@ -40,7 +41,7 @@ class RAGAPI: kb: KBService, giga: GigaClient, files: FileService, - config, + config: AppConfig, default_prompts: Optional[Dict[str, str]] = None ): """ @@ -53,7 +54,7 @@ class RAGAPI: kb: сервис базы знаний (KBService) giga: клиент GigaChat (GigaClient) files: сервис файлов (FileService) - config: объект конфигурации (BotConfig) + config: объект конфигурации (AppConfig) default_prompts: словарь промптов по умолчанию. Используется, если в запросе не переданы свои промпты. """ diff --git a/rag/rag_orchestrator.py b/rag/rag_orchestrator.py index 60e8fb1..a441548 100644 --- a/rag/rag_orchestrator.py +++ b/rag/rag_orchestrator.py @@ -8,18 +8,19 @@ import logging from typing import Optional, Dict, List, Any -from core.services.postgres_service import PostgresService -from core.services.qdrant_service import QdrantService -from core.services.embedding_service import EmbeddingService -from core.services.kb_service import KBService -from core.services.giga_client import GigaClient -from core.services.file_service import FileService -from core.history_manager import HistoryManager -from core.intent_router import IntentRouter -from core.query_processor import QueryProcessor -from core.indexing_manager import IndexingManager -from core.functions.intent_classify import classify_intent -from core.utils.text_utils import count_tokens +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 logger = logging.getLogger(__name__) @@ -39,7 +40,7 @@ class RAGOrchestrator: kb: KBService, giga: GigaClient, files: FileService, - config, + config: AppConfig, default_prompts: Optional[Dict[str, str]] = None ): """ @@ -52,7 +53,7 @@ class RAGOrchestrator: kb: сервис базы знаний giga: клиент GigaChat files: сервис файлов - config: объект конфигурации (BotConfig) + config: объект конфигурации (AppConfig) default_prompts: словарь промптов по умолчанию """ self.db = db @@ -182,9 +183,9 @@ class RAGOrchestrator: system_prompt = prompts.get('system', None) # 2. Расчёт лимитов токенов - max_model_tokens = getattr(self.config, 'max_model_tokens', 8192) - reserved_for_answer = getattr(self.config, 'reserved_for_answer_tokens', 1000) - reserved_for_overhead = getattr(self.config, 'reserved_for_overhead_tokens', 200) + 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 token_info = self._prepare_prompt_parts( synthesis_template=synthesis_template, @@ -210,7 +211,7 @@ class RAGOrchestrator: # 5. Классификация намерений intent = intent_override - if intent is None and getattr(self.config, 'enable_intent_classification', True): + if intent is None and self.config.features.enable_intent_classification: intent_prompt = prompts.get('intent', '') if intent_prompt: intent = await classify_intent( @@ -225,7 +226,7 @@ class RAGOrchestrator: intent = intent or "GENERAL" # 6. Принудительная установка SURGICAL по ключевым словам - keywords = getattr(self.config, 'surgical_keywords', []) + keywords = self.config.features.surgical_keywords if any(kw in query.lower() for kw in keywords) and last_file_path: intent = "SURGICAL" logger.info(f"Принудительный Intent: SURGICAL (есть файл и ключевые слова)") diff --git a/rag/rag_server.py b/rag/rag_server.py index cdf691b..574c2e2 100644 --- a/rag/rag_server.py +++ b/rag/rag_server.py @@ -24,7 +24,7 @@ import asyncio from pathlib import Path from typing import Optional, Dict, List, Any -# Добавляем путь к core, если запускаем из корня проекта +# Добавляем путь к rag, если запускаем из корня проекта sys.path.insert(0, str(Path(__file__).parent.parent)) from fastapi import FastAPI, HTTPException, Depends, UploadFile, File as FastAPIFile @@ -32,16 +32,16 @@ from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field import uvicorn -# Импортируем наши модули -from core.utils.config_loader import BotConfig -from core.utils.logger import setup_logging -from core.services.postgres_service import PostgresService -from core.services.qdrant_service import QdrantService -from core.services.embedding_service import EmbeddingService -from core.services.kb_service import KBService -from core.services.giga_client import GigaClient -from core.services.file_service import FileService -from core.rag_orchestrator import RAGOrchestrator +# Импортируем наши модули (теперь из пакета 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 logger = logging.getLogger(__name__) @@ -58,14 +58,10 @@ class QueryRequest(BaseModel): query: str = Field(..., description="Текст запроса пользователя") user_jid: str = Field(..., description="JID пользователя (без ресурса)") room_jid: Optional[str] = Field(None, description="JID комнаты (None для личного чата)") - - # history: List[Dict[str, str]] = Field(...) # УБРАНО! - prompts: Dict[str, str] = Field( default_factory=dict, description="Словарь промптов (system, synthesis, intent, expand, critique и т.д.)" ) - intent_override: Optional[str] = Field(None, description="Принудительное переопределение намерения") last_file_path: Optional[str] = Field(None, description="Путь к последнему загруженному файлу") last_file_text: Optional[str] = Field(None, description="Текст последнего загруженного файла") @@ -118,7 +114,7 @@ class TranscribeResponse(BaseModel): # ============================================================ _orchestrator: Optional[RAGOrchestrator] = None -_config: Optional[BotConfig] = None +_config: Optional[AppConfig] = None def get_orchestrator() -> RAGOrchestrator: @@ -129,7 +125,7 @@ def get_orchestrator() -> RAGOrchestrator: return _orchestrator -def init_orchestrator(config: BotConfig) -> RAGOrchestrator: +def init_orchestrator(config: AppConfig) -> RAGOrchestrator: """Инициализирует все сервисы и создаёт RAGOrchestrator.""" global _orchestrator, _config _config = config @@ -138,31 +134,31 @@ def init_orchestrator(config: BotConfig) -> RAGOrchestrator: # ---- 1. Инициализация сервисов ---- # PostgreSQL (история, документы) db = PostgresService( - host=config.db_host, - port=config.db_port, - user=config.db_user, + host=config.database.host, + port=config.database.port, + user=config.database.user, password=config.db_password, - db_name=config.db_name + db_name=config.database.database ) # 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 + model=config.embedding.model, + timeout=config.embedding.timeout, + cache_size=config.embedding.cache_size, + verify_ssl=config.embedding.verify_ssl_certs ) # База знаний (индексация, поиск) @@ -170,19 +166,19 @@ def init_orchestrator(config: BotConfig) -> RAGOrchestrator: db=db, qdrant=qdrant, embedding=embedding, - 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 + 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 ) # GigaChat клиент (генерация) giga = GigaClient( api_key=config.gigachat_api_key, - model=config.ai_model, - temperature=config.ai_temperature, - timeout=config.ai_timeout, + model=config.gigachat.model_generation, + temperature=config.gigachat.temperature, + timeout=config.gigachat.timeout, verify_ssl=False ) @@ -190,15 +186,11 @@ def init_orchestrator(config: BotConfig) -> RAGOrchestrator: files = FileService(config) # ---- 2. Создание оркестратора ---- - # Загружаем дефолтные промпты (опционально) - 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}") + # Загружаем дефолтные промпты (из config.prompts_content) + default_prompts = config.prompts_content or {} + system_prompt = default_prompts.get('system') + if system_prompt: + default_prompts['system'] = system_prompt orchestrator = RAGOrchestrator( db=db, @@ -444,16 +436,18 @@ def main(): ) args = parser.parse_args() - # Загружаем конфигурацию + # Загружаем конфигурацию с помощью новой функции load_config try: - config = BotConfig(args.profile_dir) + config = load_config(args.profile_dir) except Exception as e: print(f"Ошибка загрузки конфигурации: {e}") sys.exit(1) # Настраиваем логирование log_level = getattr(logging, args.log_level.upper(), logging.INFO) - setup_logging(config.name, config.log_file, 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"Хост: {args.host}, порт: {args.port}") @@ -467,7 +461,7 @@ def main(): # Запускаем Uvicorn uvicorn.run( - "core.rag_server:app", + "rag.rag_server:app", # теперь модуль из пакета rag host=args.host, port=args.port, log_level=args.log_level,