Initial commit: TrueConf Chatbot КЛЕВЕР
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import pickle # Библиотека для сохранения фрагментов текста на диск
|
||||
|
||||
# Загрузчики и разбивка текста
|
||||
from langchain_community.document_loaders import DirectoryLoader, PyMuPDFLoader, Docx2txtLoader
|
||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||
|
||||
# Эмбеддинги и векторная база
|
||||
from langchain_community.embeddings import HuggingFaceEmbeddings
|
||||
from langchain_community.vectorstores import FAISS
|
||||
|
||||
# Ретриверы
|
||||
from langchain_community.retrievers import BM25Retriever
|
||||
from langchain_classic.retrievers import EnsembleRetriever
|
||||
|
||||
# Цепочки
|
||||
from langchain_classic.chains.combine_documents import create_stuff_documents_chain
|
||||
from langchain_classic.chains import create_retrieval_chain
|
||||
|
||||
# Базовые модули и LLM
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# ==========================================
|
||||
# НАСТРОЙКА ПУТЕЙ
|
||||
# ==========================================
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BASE_REGULATIONS_PATH = os.path.join(BASE_DIR, "search_bot", "Регламенты", "АОХКСибцем", "07 Служба Вице-президента по экономике и финансам", "ДИТ")
|
||||
MODEL_PATH = os.path.join(BASE_DIR, "models", "mpnet_model")
|
||||
|
||||
# Пути для сохранения базы данных
|
||||
FAISS_DB_PATH = os.path.join(BASE_DIR, "search_bot", "faiss_index")
|
||||
CHUNKS_PATH = os.path.join(BASE_DIR, "search_bot", "chunks.pkl")
|
||||
|
||||
# ==========================================
|
||||
# 1. ИНИЦИАЛИЗАЦИЯ ЭМБЕДДИНГОВ
|
||||
# ==========================================
|
||||
print("Загрузка модели эмбеддингов (оффлайн)...")
|
||||
embedding = HuggingFaceEmbeddings(
|
||||
model_name=MODEL_PATH,
|
||||
model_kwargs={'device': 'cpu'},
|
||||
encode_kwargs={'normalize_embeddings': False}
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# 2. ЗАГРУЗКА ИЛИ СОЗДАНИЕ БАЗЫ ДАННЫХ
|
||||
# ==========================================
|
||||
# Проверяем, существует ли уже сохраненная база
|
||||
if os.path.exists(FAISS_DB_PATH) and os.path.exists(CHUNKS_PATH):
|
||||
print("✅ Найдена сохраненная база данных! Загружаю с диска (это быстро)...")
|
||||
|
||||
# Загружаем фрагменты текста для BM25
|
||||
with open(CHUNKS_PATH, 'rb') as f:
|
||||
split_docs = pickle.load(f)
|
||||
|
||||
# Загружаем векторы FAISS
|
||||
vector_store = FAISS.load_local(
|
||||
FAISS_DB_PATH,
|
||||
embedding,
|
||||
allow_dangerous_deserialization=True # Обязательно для локальных файлов
|
||||
)
|
||||
else:
|
||||
print("⚠️ Сохраненная база не найдена. Начинаю чтение и индексацию документов...\n")
|
||||
|
||||
if not os.path.exists(BASE_REGULATIONS_PATH):
|
||||
print("❌ ОШИБКА: Указанный путь к документам не существует.")
|
||||
sys.exit(1)
|
||||
|
||||
# Загружаем PDF и DOCX
|
||||
pdf_loader = DirectoryLoader(BASE_REGULATIONS_PATH, glob="**/*.pdf", loader_cls=PyMuPDFLoader, show_progress=True)
|
||||
docx_loader = DirectoryLoader(BASE_REGULATIONS_PATH, glob="**/*.docx", loader_cls=Docx2txtLoader, show_progress=True)
|
||||
|
||||
docs = pdf_loader.load() + docx_loader.load()
|
||||
if len(docs) == 0:
|
||||
print("❌ В указанной папке нет файлов .pdf или .docx. Завершение.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"✅ Загружено страниц/файлов: {len(docs)}. Разбивка на фрагменты...")
|
||||
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=200, separators=["\n\n", "\n", ".", " "])
|
||||
split_docs = text_splitter.split_documents(docs)
|
||||
|
||||
print("Создаю векторную базу FAISS...")
|
||||
vector_store = FAISS.from_documents(split_docs, embedding=embedding)
|
||||
|
||||
# СОХРАНЕНИЕ НА ДИСК ДЛЯ БУДУЩИХ ЗАПУСКОВ
|
||||
print("💾 Сохраняю базу данных на диск...")
|
||||
vector_store.save_local(FAISS_DB_PATH)
|
||||
with open(CHUNKS_PATH, 'wb') as f:
|
||||
pickle.dump(split_docs, f)
|
||||
print("✅ База успешно сохранена!")
|
||||
|
||||
# ==========================================
|
||||
# 3. НАСТРОЙКА РЕТРИВЕРОВ (ПОИСКА)
|
||||
# ==========================================
|
||||
# 3.1 Семантический поиск
|
||||
embedding_retriever = vector_store.as_retriever(search_kwargs={"k": 4})
|
||||
|
||||
# 3.2 Лексический поиск
|
||||
def tokenize(s):
|
||||
return s.lower().translate(str.maketrans("", "", string.punctuation)).split(" ")
|
||||
|
||||
bm25_retriever = BM25Retriever.from_documents(
|
||||
documents=split_docs,
|
||||
preprocess_func=tokenize,
|
||||
k=5
|
||||
)
|
||||
|
||||
# 3.3 Гибридный поиск (Ансамбль)
|
||||
ensemble_retriever = EnsembleRetriever(
|
||||
retrievers=[embedding_retriever, bm25_retriever],
|
||||
weights=[0.4, 0.6]
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# 4. ПОДКЛЮЧЕНИЕ К LLAMA.CPP
|
||||
# ==========================================
|
||||
print("Подключение к серверу llama.cpp...")
|
||||
llm = ChatOpenAI(
|
||||
base_url="http://127.0.0.1:8080/v1",
|
||||
api_key="not-needed",
|
||||
temperature=0.0,
|
||||
max_tokens=1024
|
||||
)
|
||||
|
||||
prompt = ChatPromptTemplate.from_template('''Ты — строгий корпоративный ИИ-помощник.
|
||||
Твоя задача — отвечать на вопросы строго на основании текста предоставленных регламентов.
|
||||
Внимательно изучи контекст. Если там есть ответ, сформулируй его четко и по делу. Обязательно указывай номер пункта или название документа, откуда взята информация.
|
||||
Если в контексте НЕТ ответа на вопрос, не придумывай информацию, а выведи фразу: "В предоставленных регламентах нет информации по данному вопросу."
|
||||
|
||||
Контекст:
|
||||
{context}
|
||||
|
||||
Вопрос пользователя: {input}
|
||||
|
||||
Ответ:'''
|
||||
)
|
||||
|
||||
document_chain = create_stuff_documents_chain(llm=llm, prompt=prompt)
|
||||
rag_chain = create_retrieval_chain(ensemble_retriever, document_chain)
|
||||
|
||||
# ==========================================
|
||||
# 5. ТЕСТИРОВАНИЕ СИСТЕМЫ
|
||||
# ==========================================
|
||||
print("\n🚀 Система готова к работе! Выполняем запросы...\n")
|
||||
|
||||
# Тестовые вопросы по Положению № ПОЛ-177
|
||||
questions = [
|
||||
"Где работник обязан хранить электронные документы, связанные с производственной деятельностью?",
|
||||
"Через какой срок удаляются электронные документы, если их не открывали, и можно ли их восстановить?",
|
||||
"Кому разрешен доступ к сетевым ресурсам Soft/Distr?",
|
||||
"Какие правила хранения и удаления установлены для медиафайлов в сетевой папке 'СВК'?",
|
||||
"Разрешено ли самостоятельно предоставлять общий доступ к папкам на своем рабочем компьютере?",
|
||||
"Что имеют право сделать сотрудники ДИТ или СИБ, если файл несет угрозу ресурсам?"
|
||||
]
|
||||
|
||||
for q in questions:
|
||||
print(f"❓ Вопрос: {q}")
|
||||
try:
|
||||
response = rag_chain.invoke({'input': q})
|
||||
print(f"🤖 Ответ: {response['answer']}\n")
|
||||
|
||||
# Распечатка источников (полезно для отладки)
|
||||
print("🔍 Найденные источники:")
|
||||
for i, doc in enumerate(response['context']):
|
||||
source_name = doc.metadata.get('source', 'Неизвестный источник').split('/')[-1] # Берем только имя файла
|
||||
print(f" [{i+1}] Файл: {source_name}")
|
||||
print("\n" + "-" * 50 + "\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при запросе к серверу llama.cpp: {e}")
|
||||
print("-" * 50)
|
||||
Reference in New Issue
Block a user