Update 86 files

- /rag/commands/expert.py
- /core/services/__init__.py
- /core/services/embedding_service.py
- /core/services/file_service.py
- /core/services/giga_client.py
- /core/services/kb_service.py
- /core/services/postgres_service.py
- /core/services/qdrant_service.py
- /core/services/reranker_service.py
- /core/functions/__init__.py
- /core/functions/check_consistency.py
- /core/functions/check_spelling.py
- /core/functions/critique_answer.py
- /core/functions/expand_query.py
- /core/functions/extract_metrics.py
- /core/functions/file_processor.py
- /core/functions/generate_document.py
- /core/functions/intent_classify.py
- /core/functions/rerank_context.py
- /core/functions/summarize_document.py
- /core/utils/__init__.py
- /core/utils/arg_parser.py
- /core/utils/config_loader.py
- /core/utils/layout_converter.py
- /core/utils/logger.py
- /core/utils/text_utils.py
- /core/utils/web_utils.py
- /bots/commands/__init__.py
- /bots/commands/base.py
- /bots/commands/create.py
- /core/commands/global_remove.py
- /core/commands/help.py
- /core/commands/info.py
- /core/commands/kb.py
- /core/commands/learn.py
- /core/commands/other.py
- /core/commands/registry.py
- /core/commands/stats.py
- /core/commands/template.py
- /core/handlers/__init__.py
- /core/handlers/message_handler.py
- /core/workers/__init__.py
- /core/workers/indexing_worker.py
- /core/xmpp/__init__.py
- /core/xmpp/client.py
- /bots/commands/global_remove.py
- /bots/commands/help.py
- /bots/commands/info.py
- /bots/commands/kb.py
- /bots/commands/registry.py
- /bots/commands/other.py
- /bots/commands/template.py
- /bots/commands/learn.py
- /bots/commands/stats.py
- /bots/handlers/message_handler.py
- /bots/handlers/__init__.py
- /bots/workers/__init__.py
- /bots/workers/indexing_worker.py
- /bots/xmpp/__init__.py
- /bots/xmpp/client.py
- /rag/services/qdrant_service.py
- /rag/services/giga_client.py
- /rag/services/embedding_service.py
- /rag/services/reranker_service.py
- /rag/services/__init__.py
- /rag/services/file_service.py
- /rag/services/postgres_service.py
- /rag/services/kb_service.py
- /rag/functions/check_spelling.py
- /rag/functions/__init__.py
- /rag/functions/extract_metrics.py
- /rag/functions/generate_document.py
- /rag/functions/expand_query.py
- /rag/functions/critique_answer.py
- /rag/functions/file_processor.py
- /rag/functions/check_consistency.py
- /rag/functions/summarize_document.py
- /rag/functions/rerank_context.py
- /rag/functions/intent_classify.py
- /rag/utils/__init__.py
- /rag/utils/layout_converter.py
- /rag/utils/text_utils.py
- /rag/utils/arg_parser.py
- /rag/utils/config_loader.py
- /rag/utils/logger.py
- /rag/utils/web_utils.py
This commit is contained in:
Markov Andrey
2026-06-30 10:33:28 +00:00
parent ea0d42de9c
commit 63900feece
45 changed files with 0 additions and 265 deletions

View File

@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
"""
Модуль генерации документа по шаблону.
Заполняет плейсхолдеры {{...}} на основе данных из базы знаний.
АДАПТАЦИЯ: теперь функция принимает промпт и конфиг как аргументы.
"""
import re
import asyncio
import os
import logging
import shutil
from docx import Document
from core.functions.file_processor import send_file_to_user
logger = logging.getLogger(__name__)
async def generate_document_from_template(
bot,
msg,
room_jid,
template_path,
template_name,
user_jid,
giga,
kb,
config,
prompt_text,
temperature=0.1,
allow_public=False
):
"""
Фоновая задача: парсит шаблон, ищет плейсхолдеры {{...}},
для каждого делает контекстный поиск в БЗ комнаты, генерирует значение через LLM,
заменяет в копии шаблона и отправляет готовый файл в комнату.
Аргументы:
bot: экземпляр бота
msg: объект XMPP-сообщения
room_jid: JID комнаты
template_path: путь к шаблону
template_name: имя шаблона
user_jid: JID пользователя
giga: клиент GigaChat
kb: сервис базы знаний
config: объект конфигурации
prompt_text: содержимое промпта для генерации
temperature: температура генерации
allow_public: разрешено ли использовать общие знания
"""
logger.info(f"🚀 [ГЕНЕРАЦИЯ] Запущена для шаблона '{template_name}' в комнате {room_jid}")
try:
doc = Document(template_path)
# Собираем все плейсхолдеры и их контекст
placeholders_info = {}
for paragraph in doc.paragraphs:
for match in re.finditer(r'\{\{([^}]+)\}\}', paragraph.text):
ph = match.group(1)
if ph not in placeholders_info:
placeholders_info[ph] = paragraph.text
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for match in re.finditer(r'\{\{([^}]+)\}\}', cell.text):
ph = match.group(1)
if ph not in placeholders_info:
placeholders_info[ph] = cell.text
placeholders = set(placeholders_info.keys())
logger.info(f"🔍 [ГЕНЕРАЦИЯ] Найдено плейсхолдеров: {len(placeholders)}")
if not placeholders:
reply = msg.reply(f"В шаблоне '{template_name}' нет плейсхолдеров {{...}}.")
if reply:
reply.send()
return
generated = {}
for ph, surrounding in placeholders_info.items():
logger.info(f"📖 [ГЕНЕРАЦИЯ] Обработка плейсхолдера '{ph}'")
# Формируем поисковый запрос
search_query = f"{ph} {surrounding[:100]}"
context = await kb.find_relevant_info(search_query, user_jid, room_jid, top_k=20)
if not context and allow_public:
logger.info(f"⚠️ [ГЕНЕРАЦИЯ] Контекст для '{ph}' не найден, используем общие знания")
prompt = f"Ты — эксперт. Ответь на вопрос: {ph}. Дай краткое значение, без пояснений."
try:
response = await giga.chat(
history=[],
query=prompt,
system_prompt=None,
file_id=None,
temperature=temperature
)
generated[ph] = response.strip()
continue
except Exception as e:
logger.error(f"❌ [ГЕНЕРАЦИЯ] Ошибка LLM для '{ph}': {e}")
generated[ph] = "[ошибка]"
continue
if not context:
logger.warning(f"⚠️ [ГЕНЕРАЦИЯ] Нет контекста для '{ph}', и общие знания запрещены")
generated[ph] = "не определено"
continue
# Генерация значения на основе контекста
prompt = prompt_text.format(ph=ph, context=context)
try:
response = await giga.chat(
history=[],
query=prompt,
system_prompt=None,
file_id=None,
temperature=temperature
)
cleaned = re.sub(r'^```\w*\n?|```$', '', response.strip())
generated[ph] = cleaned
logger.info(f"✅ [ГЕНЕРАЦИЯ] Значение для '{ph}': {generated[ph][:100]}")
except Exception as e:
logger.error(f"❌ [ГЕНЕРАЦИЯ] Ошибка LLM для '{ph}': {e}", exc_info=True)
generated[ph] = "[ошибка]"
# Замена плейсхолдеров в копии документа
temp_dir = config.temp_dir
new_path = temp_dir / f"generated_{template_name}_{int(asyncio.get_event_loop().time())}.docx"
shutil.copy2(template_path, new_path)
new_doc = Document(new_path)
def replace_in_paragraph(paragraph, replacements):
full_text = ''.join(run.text for run in paragraph.runs)
modified = False
for key, value in replacements.items():
placeholder = f"{{{{{key}}}}}"
if placeholder in full_text:
for run in paragraph.runs:
if placeholder in run.text:
run.text = run.text.replace(placeholder, value)
modified = True
if not modified:
for key, value in replacements.items():
placeholder = f"{{{{{key}}}}}"
if placeholder in paragraph.text:
paragraph.text = paragraph.text.replace(placeholder, value)
def walk_document(doc_obj, replacements):
for paragraph in doc_obj.paragraphs:
replace_in_paragraph(paragraph, replacements)
for table in doc_obj.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
replace_in_paragraph(paragraph, replacements)
walk_document(new_doc, generated)
new_doc.save(new_path)
await send_file_to_user(bot, msg, str(new_path), prefix=f"📄 Сгенерированный документ по шаблону '{template_name}':", suffix="generated")
try:
os.unlink(new_path)
except Exception as e:
logger.warning(f"Не удалось удалить временный файл {new_path}: {e}")
logger.info(f"🏁 [ГЕНЕРАЦИЯ] Завершено для шаблона '{template_name}'")
except Exception as e:
logger.exception(f"💥 [ГЕНЕРАЦИЯ] Критическая ошибка: {e}")
if msg:
reply = msg.reply(f"❌ Ошибка при генерации документа: {e}")
if reply:
reply.send()