Files
fckbot/rag/functions/generate_document.py
Markov Andrey 63900feece 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
2026-06-30 10:33:28 +00:00

180 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()