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
This commit is contained in:
Markov Andrey
2026-06-30 14:04:31 +00:00
parent c9ee8fc19a
commit a3f52888db
6 changed files with 107 additions and 114 deletions

View File

@@ -1,16 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Пакет core общий код для всех компонентов платформы Эфцекабот. Пакет rag RAG-ядро платформы Эфцекабот.
Содержит: Содержит:
- RAG-сервер и оркестратор, - RAG-сервер и оркестратор,
- HTTP-клиент для ботов,
- сервисы (PostgreSQL, Qdrant, GigaChat), - сервисы (PostgreSQL, Qdrant, GigaChat),
- функции (классификация, суммаризация, проверка орфографии и др.), - функции (классификация, суммаризация, проверка орфографии и др.),
- обработчики XMPP-событий,
- команды ботов,
- фоновые воркеры,
- утилиты. - утилиты.
Используется как RAG-ядром, так и тонкими XMPP-клиентами-профилями. Используется как HTTP-сервис.
""" """
__version__ = "3.0.0" __version__ = "3.0.0"

View File

@@ -13,10 +13,11 @@
import logging import logging
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
from core.services.postgres_service import PostgresService from rag.services.postgres_service import PostgresService
from core.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
from core.utils.text_utils import count_tokens from rag.utils.text_utils import count_tokens
from core.functions.hierarchical_summarize import hierarchical_summarize from rag.functions.hierarchical_summarize import hierarchical_summarize
from rag.config_models import AppConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -30,7 +31,7 @@ class HistoryManager:
self, self,
db: PostgresService, db: PostgresService,
giga: GigaClient, giga: GigaClient,
config, config: AppConfig,
default_prompts: Optional[Dict[str, str]] = None default_prompts: Optional[Dict[str, str]] = None
): ):
""" """
@@ -39,7 +40,7 @@ class HistoryManager:
Аргументы: Аргументы:
db: сервис PostgreSQL (для получения и сохранения истории) db: сервис PostgreSQL (для получения и сохранения истории)
giga: клиент GigaChat (для сжатия истории) giga: клиент GigaChat (для сжатия истории)
config: объект конфигурации (BotConfig) config: объект конфигурации (AppConfig)
default_prompts: словарь промптов по умолчанию (нужен для hierarchical_summary) default_prompts: словарь промптов по умолчанию (нужен для hierarchical_summary)
""" """
self.db = db self.db = db
@@ -47,10 +48,6 @@ class HistoryManager:
self.config = config self.config = config
self.default_prompts = default_prompts or {} self.default_prompts = default_prompts or {}
# ------------------------------------------------------------------
# Получение и форматирование истории
# ------------------------------------------------------------------
async def get_history( async def get_history(
self, self,
user_jid: str, user_jid: str,
@@ -116,7 +113,7 @@ class HistoryManager:
if not prompt_template: if not prompt_template:
# Пытаемся загрузить из файла, если он есть # Пытаемся загрузить из файла, если он есть
try: 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: with open(prompt_path, 'r', encoding='utf-8') as f:
prompt_template = f.read() prompt_template = f.read()
except Exception as e: except Exception as e:
@@ -130,11 +127,11 @@ class HistoryManager:
) )
# Параметры сжатия из конфига # Параметры сжатия из конфига
summarization_config = getattr(self.config, 'summarization', {}) summarization_config = self.config.summarization
target_tokens = summarization_config.get('target_tokens_after_summary', max_tokens) target_tokens = summarization_config.target_tokens_after_summary
chunk_size = summarization_config.get('chunk_size_tokens', 500) chunk_size = summarization_config.chunk_size_tokens
max_depth = summarization_config.get('max_depth', 2) max_depth = summarization_config.max_depth
temperature = getattr(self.config, 'ai_temperature', 0.1) temperature = self.config.ai.temperature
try: try:
compressed_text = await hierarchical_summarize( compressed_text = await hierarchical_summarize(

View File

@@ -9,12 +9,13 @@ import logging
import re import re
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any
from core.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
from core.services.kb_service import KBService from rag.services.kb_service import KBService
from core.functions.expand_query import expand_query from rag.functions.expand_query import expand_query
from core.functions.rerank_context import rerank_context from rag.functions.rerank_context import rerank_context
from core.functions.critique_answer import critique_answer from rag.functions.critique_answer import critique_answer
from core.utils.text_utils import count_tokens from rag.utils.text_utils import count_tokens
from rag.config_models import AppConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,7 +29,7 @@ class QueryProcessor:
self, self,
giga: GigaClient, giga: GigaClient,
kb: KBService, kb: KBService,
config, config: AppConfig,
default_prompts: Optional[Dict[str, str]] = None default_prompts: Optional[Dict[str, str]] = None
): ):
""" """
@@ -37,7 +38,7 @@ class QueryProcessor:
Аргументы: Аргументы:
giga: клиент GigaChat giga: клиент GigaChat
kb: сервис базы знаний kb: сервис базы знаний
config: объект конфигурации config: объект конфигурации (AppConfig)
default_prompts: словарь промптов по умолчанию default_prompts: словарь промптов по умолчанию
""" """
self.giga = giga self.giga = giga
@@ -73,18 +74,20 @@ class QueryProcessor:
Словарь с ключами 'answer', 'context', 'sources', 'confidence' Словарь с ключами 'answer', 'context', 'sources', 'confidence'
""" """
# 1. Расширение запроса # 1. Расширение запроса
expand_prompt = prompts.get('expand', '')
expanded = await expand_query( expanded = await expand_query(
giga=self.giga, giga=self.giga,
query=query, query=query,
prompt_text=prompts.get('expand', ''), prompt_text=expand_prompt,
bot_config=self.config bot_config=self.config
) )
search_query = expanded if expanded and expanded != query else query search_query = expanded if expanded and expanded != query else query
# 2. Поиск релевантного контекста в базе знаний # 2. Поиск релевантного контекста в базе знаний
top_k = self.config.rag.default_top_k
context = await self.kb.find_relevant_info( context = await self.kb.find_relevant_info(
search_query, user_jid, room_jid, 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})") logger.info(f"Найден контекст длиной {len(context)} символов (room={room_jid})")
@@ -103,7 +106,7 @@ class QueryProcessor:
context = "" context = ""
# 4. Переранжирование контекста (если включено и контекст достаточно длинный) # 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: if intent != "FACT" and len(context) > rerank_min_length:
context = await rerank_context( context = await rerank_context(
bot=None, bot=None,
@@ -130,16 +133,17 @@ class QueryProcessor:
logger.debug(f"Полный запрос к GigaChat (первые 500 символов): {full_query[:500]}") logger.debug(f"Полный запрос к GigaChat (первые 500 символов): {full_query[:500]}")
# 6. Генерация ответа # 6. Генерация ответа
temperature = self.config.ai.temperature
answer = await self.giga.chat( answer = await self.giga.chat(
history=history, history=history,
query=full_query, query=full_query,
system_prompt=system_prompt, system_prompt=system_prompt,
file_id=None, file_id=None,
temperature=getattr(self.config, 'ai_temperature', 0.1) temperature=temperature
) )
# 7. Самокритика (если включена) # 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', '') critique_prompt = prompts.get('critique', '')
if critique_prompt: if critique_prompt:
logger.debug("Запуск самокритики") logger.debug("Запуск самокритики")
@@ -159,7 +163,7 @@ class QueryProcessor:
query=full_query_retry, query=full_query_retry,
system_prompt=system_prompt, system_prompt=system_prompt,
file_id=None, file_id=None,
temperature=getattr(self.config, 'ai_temperature', 0.1) temperature=temperature
) )
# Повторная проверка после перегенерации # Повторная проверка после перегенерации
if not await critique_answer( if not await critique_answer(

View File

@@ -16,11 +16,12 @@
import logging import logging
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any
from core.rag_orchestrator import RAGOrchestrator from rag.rag_orchestrator import RAGOrchestrator
from core.services.kb_service import KBService from rag.services.kb_service import KBService
from core.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
from core.services.file_service import FileService from rag.services.file_service import FileService
from core.services.postgres_service import PostgresService from rag.services.postgres_service import PostgresService
from rag.config_models import AppConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -40,7 +41,7 @@ class RAGAPI:
kb: KBService, kb: KBService,
giga: GigaClient, giga: GigaClient,
files: FileService, files: FileService,
config, config: AppConfig,
default_prompts: Optional[Dict[str, str]] = None default_prompts: Optional[Dict[str, str]] = None
): ):
""" """
@@ -53,7 +54,7 @@ class RAGAPI:
kb: сервис базы знаний (KBService) kb: сервис базы знаний (KBService)
giga: клиент GigaChat (GigaClient) giga: клиент GigaChat (GigaClient)
files: сервис файлов (FileService) files: сервис файлов (FileService)
config: объект конфигурации (BotConfig) config: объект конфигурации (AppConfig)
default_prompts: словарь промптов по умолчанию. default_prompts: словарь промптов по умолчанию.
Используется, если в запросе не переданы свои промпты. Используется, если в запросе не переданы свои промпты.
""" """

View File

@@ -8,18 +8,19 @@
import logging import logging
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any
from core.services.postgres_service import PostgresService from rag.services.postgres_service import PostgresService
from core.services.qdrant_service import QdrantService from rag.services.qdrant_service import QdrantService
from core.services.embedding_service import EmbeddingService from rag.services.embedding_service import EmbeddingService
from core.services.kb_service import KBService from rag.services.kb_service import KBService
from core.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
from core.services.file_service import FileService from rag.services.file_service import FileService
from core.history_manager import HistoryManager from rag.history_manager import HistoryManager
from core.intent_router import IntentRouter from rag.intent_router import IntentRouter
from core.query_processor import QueryProcessor from rag.query_processor import QueryProcessor
from core.indexing_manager import IndexingManager from rag.indexing_manager import IndexingManager
from core.functions.intent_classify import classify_intent from rag.functions.intent_classify import classify_intent
from core.utils.text_utils import count_tokens from rag.utils.text_utils import count_tokens
from rag.config_models import AppConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -39,7 +40,7 @@ class RAGOrchestrator:
kb: KBService, kb: KBService,
giga: GigaClient, giga: GigaClient,
files: FileService, files: FileService,
config, config: AppConfig,
default_prompts: Optional[Dict[str, str]] = None default_prompts: Optional[Dict[str, str]] = None
): ):
""" """
@@ -52,7 +53,7 @@ class RAGOrchestrator:
kb: сервис базы знаний kb: сервис базы знаний
giga: клиент GigaChat giga: клиент GigaChat
files: сервис файлов files: сервис файлов
config: объект конфигурации (BotConfig) config: объект конфигурации (AppConfig)
default_prompts: словарь промптов по умолчанию default_prompts: словарь промптов по умолчанию
""" """
self.db = db self.db = db
@@ -182,9 +183,9 @@ class RAGOrchestrator:
system_prompt = prompts.get('system', None) system_prompt = prompts.get('system', None)
# 2. Расчёт лимитов токенов # 2. Расчёт лимитов токенов
max_model_tokens = getattr(self.config, 'max_model_tokens', 8192) max_model_tokens = self.config.rag.max_model_tokens
reserved_for_answer = getattr(self.config, 'reserved_for_answer_tokens', 1000) reserved_for_answer = self.config.rag.reserved_for_answer_tokens
reserved_for_overhead = getattr(self.config, 'reserved_for_overhead_tokens', 200) reserved_for_overhead = self.config.rag.reserved_for_overhead_tokens
token_info = self._prepare_prompt_parts( token_info = self._prepare_prompt_parts(
synthesis_template=synthesis_template, synthesis_template=synthesis_template,
@@ -210,7 +211,7 @@ class RAGOrchestrator:
# 5. Классификация намерений # 5. Классификация намерений
intent = intent_override 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', '') intent_prompt = prompts.get('intent', '')
if intent_prompt: if intent_prompt:
intent = await classify_intent( intent = await classify_intent(
@@ -225,7 +226,7 @@ class RAGOrchestrator:
intent = intent or "GENERAL" intent = intent or "GENERAL"
# 6. Принудительная установка SURGICAL по ключевым словам # 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: 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 (есть файл и ключевые слова)")

View File

@@ -24,7 +24,7 @@ import asyncio
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any
# Добавляем путь к core, если запускаем из корня проекта # Добавляем путь к rag, если запускаем из корня проекта
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from fastapi import FastAPI, HTTPException, Depends, UploadFile, File as FastAPIFile 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 from pydantic import BaseModel, Field
import uvicorn import uvicorn
# Импортируем наши модули # Импортируем наши модули (теперь из пакета rag)
from core.utils.config_loader import BotConfig from rag.utils.config_loader import load_config
from core.utils.logger import setup_logging from rag.utils.logger import setup_logging
from core.services.postgres_service import PostgresService from rag.services.postgres_service import PostgresService
from core.services.qdrant_service import QdrantService from rag.services.qdrant_service import QdrantService
from core.services.embedding_service import EmbeddingService from rag.services.embedding_service import EmbeddingService
from core.services.kb_service import KBService from rag.services.kb_service import KBService
from core.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
from core.services.file_service import FileService from rag.services.file_service import FileService
from core.rag_orchestrator import RAGOrchestrator from rag.rag_orchestrator import RAGOrchestrator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,14 +58,10 @@ class QueryRequest(BaseModel):
query: str = Field(..., description="Текст запроса пользователя") query: str = Field(..., description="Текст запроса пользователя")
user_jid: str = Field(..., description="JID пользователя (без ресурса)") user_jid: str = Field(..., description="JID пользователя (без ресурса)")
room_jid: Optional[str] = Field(None, description="JID комнаты (None для личного чата)") room_jid: Optional[str] = Field(None, description="JID комнаты (None для личного чата)")
# history: List[Dict[str, str]] = Field(...) # УБРАНО!
prompts: Dict[str, str] = Field( prompts: Dict[str, str] = Field(
default_factory=dict, default_factory=dict,
description="Словарь промптов (system, synthesis, intent, expand, critique и т.д.)" description="Словарь промптов (system, synthesis, intent, expand, critique и т.д.)"
) )
intent_override: Optional[str] = Field(None, description="Принудительное переопределение намерения") intent_override: Optional[str] = Field(None, description="Принудительное переопределение намерения")
last_file_path: Optional[str] = Field(None, description="Путь к последнему загруженному файлу") last_file_path: Optional[str] = Field(None, description="Путь к последнему загруженному файлу")
last_file_text: Optional[str] = Field(None, description="Текст последнего загруженного файла") last_file_text: Optional[str] = Field(None, description="Текст последнего загруженного файла")
@@ -118,7 +114,7 @@ class TranscribeResponse(BaseModel):
# ============================================================ # ============================================================
_orchestrator: Optional[RAGOrchestrator] = None _orchestrator: Optional[RAGOrchestrator] = None
_config: Optional[BotConfig] = None _config: Optional[AppConfig] = None
def get_orchestrator() -> RAGOrchestrator: def get_orchestrator() -> RAGOrchestrator:
@@ -129,7 +125,7 @@ def get_orchestrator() -> RAGOrchestrator:
return _orchestrator return _orchestrator
def init_orchestrator(config: BotConfig) -> RAGOrchestrator: def init_orchestrator(config: AppConfig) -> RAGOrchestrator:
"""Инициализирует все сервисы и создаёт RAGOrchestrator.""" """Инициализирует все сервисы и создаёт RAGOrchestrator."""
global _orchestrator, _config global _orchestrator, _config
_config = config _config = config
@@ -138,31 +134,31 @@ def init_orchestrator(config: BotConfig) -> RAGOrchestrator:
# ---- 1. Инициализация сервисов ---- # ---- 1. Инициализация сервисов ----
# PostgreSQL (история, документы) # PostgreSQL (история, документы)
db = PostgresService( db = PostgresService(
host=config.db_host, host=config.database.host,
port=config.db_port, port=config.database.port,
user=config.db_user, user=config.database.user,
password=config.db_password, password=config.db_password,
db_name=config.db_name db_name=config.database.database
) )
# 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 verify_ssl=config.embedding.verify_ssl_certs
) )
# База знаний (индексация, поиск) # База знаний (индексация, поиск)
@@ -170,19 +166,19 @@ def init_orchestrator(config: BotConfig) -> 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.chunk_size_tokens, chunk_size_tokens=config.chunking.chunk_size_tokens,
overlap_tokens=config.overlap_tokens, overlap_tokens=config.chunking.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.ai_model, model=config.gigachat.model_generation,
temperature=config.ai_temperature, temperature=config.gigachat.temperature,
timeout=config.ai_timeout, timeout=config.gigachat.timeout,
verify_ssl=False verify_ssl=False
) )
@@ -190,15 +186,11 @@ def init_orchestrator(config: BotConfig) -> RAGOrchestrator:
files = FileService(config) files = FileService(config)
# ---- 2. Создание оркестратора ---- # ---- 2. Создание оркестратора ----
# Загружаем дефолтные промпты (опционально) # Загружаем дефолтные промпты (из config.prompts_content)
default_prompts = {} default_prompts = config.prompts_content or {}
system_prompt_path = getattr(config, 'system_prompt_file', None) system_prompt = default_prompts.get('system')
if system_prompt_path and system_prompt_path.exists(): if system_prompt:
try: default_prompts['system'] = system_prompt
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,
@@ -444,16 +436,18 @@ def main():
) )
args = parser.parse_args() args = parser.parse_args()
# Загружаем конфигурацию # Загружаем конфигурацию с помощью новой функции load_config
try: try:
config = BotConfig(args.profile_dir) config = load_config(args.profile_dir)
except Exception as e: except Exception as e:
print(f"Ошибка загрузки конфигурации: {e}") print(f"Ошибка загрузки конфигурации: {e}")
sys.exit(1) sys.exit(1)
# Настраиваем логирование # Настраиваем логирование
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, 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}")
@@ -467,7 +461,7 @@ def main():
# Запускаем Uvicorn # Запускаем Uvicorn
uvicorn.run( uvicorn.run(
"core.rag_server:app", "rag.rag_server:app", # теперь модуль из пакета rag
host=args.host, host=args.host,
port=args.port, port=args.port,
log_level=args.log_level, log_level=args.log_level,