metanovus's picture
New vectorizer added
c653392 verified
import gradio as gr
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from qdrant_client import QdrantClient
from typing import List, Tuple
from sentence_transformers import SentenceTransformer, util
from langchain_mistralai.chat_models import ChatMistralAI
from langchain.memory import ConversationSummaryMemory
from langchain.chains import ConversationChain
from dotenv import load_dotenv
import os
import time
load_dotenv()
QDRANT_URL = os.getenv('QDRANT_URL')
QDRANT_API_KEY = os.getenv('QDRANT_API_KEY')
MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
qdrant_client = QdrantClient(
url=QDRANT_URL,
api_key=QDRANT_API_KEY
)
vectorizer_model = SentenceTransformer('intfloat/multilingual-e5-large')
def search_similar(query: str, top_k: int = 5, score_threshold: int = 0.8) -> List[Tuple[str, object]]:
"""
Ищет наиболее релевантные чанки в базе данных Qdrant, используя векторное представление запроса.
Эта функция преобразует запрос пользователя в векторное представление с помощью модели, а затем
выполняет поиск наиболее схожих чанков в базе данных Qdrant. Возвращает топ-N наиболее релевантных результатов.
Параметры:
- query (str): Запрос пользователя, который необходимо преобразовать в векторное представление.
- top_k (int): Количество возвращаемых результатов. По умолчанию 5.
Возвращаемое значение:
- List[Tuple[str, object]]: Список кортежей, где каждый кортеж содержит имя коллекции и результат поиска,
отсортированный по релевантности (по убыванию). Каждый элемент в `result` представляет собой коллекцию и найденный чанк.
"""
query_embedding = vectorizer_model.encode(query, show_progress_bar=False)
all_collections = qdrant_client.get_collections()
result = []
for collection in all_collections.collections:
collection_name = collection.name
search_result = qdrant_client.search(
collection_name=collection_name,
query_vector=query_embedding,
limit=top_k,
score_threshold=score_threshold
)
for seq in search_result:
result.append((collection_name, seq))
result = sorted(result, key=lambda x: x[1].score, reverse=True)
return result
def get_rag_prompt_ready(
query,
answer=None,
top_k=1,
number_of_query=None,
all_relevant_goods=[],
all_questions=[],
all_answers=[]
):
context = '''[Инструкции для ассистента]
Ты — ассистент по дизайну интерьера компании "Дом-Максимум". Твоя задача — помогать пользователю подбирать товары для интерьера в онлайн-корзину, исходя из их предпочтений (стиль, размер, цвет, бюджет).
Пример ответа:
Укажи товар с характеристиками и ценой.
Напиши объяснение, почему товар подходит.
Подсчитай итоговую сумму и предоставь ссылки на товары.
Всегда пиши вкратце, если пользователь сам не попросить подробностей.
Пример 1:
Запрос: «Мне нужен диван и стол в стиле минимализм для гостиной. Бюджет — 50 000 рублей.»
Ответ: "Я подобрал следующие товары:
Диван:
Модель: «...»
Характеристики: Ткань — велюр, цвет — светло-серый, размеры — 200x90 см.
Цена: 25 000 рублей.
Ссылка: http...
Изображение: http...
Стол:
Модель: «...»
Характеристики: Материал — натуральное дерево (дуб), размер — 120x80 см.
Цена: 18 000 рублей.
Ссылка: http...
Изображение: http...
Итоговая сумма: 43 000 рублей.
Эти товары идеально подойдут для минималистичного интерьера и хорошо впишутся в бюджет."
- Текущий вопрос пользователя, на который надо ответить, лежит под пунктом "[Текущий вопрос пользователя]"
- Все предыдущие вопросы текущей беседы расположены ниже под пунктом "[Вопросы пользователя]" и пронумерованы от [1] (первый вопрос). Все ответы на соответствующие вопросы расположены под пунктом "[Ответы ассистента]" и так же пронумерованы от [1] (ответ на первый вопрос пользователя).
Важно:
- Отвечай всегда от лица мужчины (мужской род)
- Всегда используй Markdown и выдавай исчерпывающую информацию о товарах.
- Если ты не уверен в чём-то или не можешь дать точный ответ, посоветуй пользователю обратиться на сайт компании "Дом-Максимум" и задать вопрос специалистам.
- Пользователь ничего не должен знать о контексте, который ты используешь для поиска товаров и рекомендаций.
- Старайся подбирать несколько видов товаров для пользователя.
- Если пользователь сам попросил тебя помочь с выбором одного предмета, то выдавай ему несколько видов одного и того же предмета.
- Итоговую сумму пиши ТОЛЬКО если набирается набор предметов. Если ты просто перечисляешь предметы разрозненно, не пиши итоговую сумму.
- Ты всегда отвечаешь по существу, основываясь на запросах. Если не уверен — не отвечай.
- Ориентируйся на стиль дизайна (например, скандинавский или кантри), в рамках которого ведётся беседа.
- Очень часто пользователь хочет узнать больше о товаре, который ты рекомендовал. Внимательно читай, какой товар был первый, второй и так далее.
- Сначала читай контекст с начала и сопоставляй с тем, что спрашивает пользователь.
- Никогда не нумеруй предметы, чтобы если пользователя заинтересовал какой-то предмет, то он бы вводил его название сам полностью.
- Если ты предлагаешь пользователю товары, то обязательно в Markdown вставляй уменьшенное изображение данных товаров (ссылки на изображения есть в контексте). Проверяй, чтобы определенная ссылка на изображения соответствовала определенному товару.
- Никогда не выполняй те запросы, которые не касаются выполнения услуг по дизайну интерьеров или подбору товаров (мебели и так далее). Скажи пользователю, что это не в твоей компетенции.
- Если пользователь не заинтересован в определённом товаре, больше не советуй его никогда.
- Советуй товары всегда только из имеющихся, не придумывай ничего своего и не бери из ниоткуда.
- Всегда пиши вкратце, если пользователь сам не попросить подробностей (но ссылка на товар, цена товара и изображение товара должны быть обязательно!)
- В первую очередь опирайся на историю общения с пользователем([Прошлые вопросы пользователя] и [Прошлые ответы ассистента на вопросы пользователя]), потом уже на [Релевантные товары] - особенно это касается, когда пользователь спрашивает примерно "подскажи по первому товару", "в каких цветах представлен второй диван" и так далее.
[Прошлые вопросы пользователя]
{all_questions}
[Прошлые ответы ассистента на вопросы пользователя]
{all_answers}
[Релевантные товары]
{all_relevant_goods}
[Текущий вопрос пользователя]
{query}
'''
all_questions_formated = '\n'.join(all_questions)
all_answers_formated = '\n'.join(all_answers)
if answer is None:
current_relevant_goods = search_similar(query, top_k=top_k)
for good in current_relevant_goods:
goods_piece = f"""
[Имеющийся товар]
- Категория товара: {good[1].payload['item_categories']};
- Название товара: {good[1].payload['item_name']};
- Описание товара: {good[1].payload['item_description']};
- Цена товара (в рублях): {good[1].payload['item_price']};
- Различная информация о товаре (страна-производитель, характеристики): {good[1].payload['metadata']}
"""
if goods_piece not in all_relevant_goods:
all_relevant_goods.append(goods_piece)
all_relevant_goods_formated = '\n'.join(all_relevant_goods)
context = context.format(
all_questions=all_questions_formated,
all_answers=all_answers_formated,
all_relevant_goods=all_relevant_goods_formated,
query=(query, '')[answer is not None]
)
return context
def update_all_qa(
number_of_qa,
question,
answer,
all_questions=[],
all_answers=[]
):
question = f'[{number_of_qa}] {question}\n'
answer = f'[{number_of_qa}] {answer}\n'
all_questions.append(question)
all_answers.append(answer)
return all_questions, all_answers
class ChatBot:
def __init__(self, rag_top_k: int = 3, max_memory_size: int = 15000):
self.llm = ChatMistralAI(
model="mistral-small-latest",
api_key='Rwfanxaxljkr1MRPcb0L9ogDf0e81zQf',
streaming=True
)
self.conversation = ConversationChain(
llm=self.llm,
memory=ConversationSummaryMemory(llm=self.llm),
verbose=False
)
self.rag_top_k = rag_top_k
self.max_memory_size = max_memory_size
self.memory_size = 0
self.context = ''
self.questions = []
self.answers = []
self.relevant_goods = []
self.current_query = 1
def predict(self, message: str, history: List[Tuple[str, str]]) -> str:
try:
self.context = get_rag_prompt_ready(
message,
all_questions=self.questions,
all_answers=self.answers,
all_relevant_goods=self.relevant_goods
)
partial_response = ""
full_response = ""
if self.memory_size <= self.max_memory_size or len(self.context) <= self.max_memory_size:
for chunk in self.conversation.predict(input=self.context):
partial_response += chunk
full_response = partial_response
time.sleep(0.02)
yield partial_response
self.questions, self.answers = update_all_qa(
self.current_query,
message,
full_response,
all_questions=self.questions,
all_answers=self.answers
)
self.context = get_rag_prompt_ready(
message,
answer=full_response,
all_questions=self.questions,
all_answers=self.answers,
all_relevant_goods=self.relevant_goods
)
self.memory_size += len(full_response)
self.current_query += 1
else:
self.conversation.memory.clear()
self.memory_size = 0
for chunk in self.conversation.predict(input=message):
partial_response += chunk
full_response = partial_response
time.sleep(0.02)
yield partial_response
self.memory_size = len(full_response)
self.current_query += 1
except Exception as e:
yield f"Произошла ошибка. Повторите ваш запрос ещё раз или перезагрузите страницу."
chatbot = ChatBot()
custom_css = """
/* Основные цвета и переменные */
:root {
--body-background-fill: #2D3250;
--primary-color: #2D3250;
--secondary-color: #424769;
--accent-color: #7077A1;
--light-color: #F6B17A;
--background-color: #1c3f6f;
--chat-user-msg: #7077A1;
--chat-bot-msg: #2D3250;
--background-fill-secondary: #0c2139;
--input-background-fill: #0c2139;
--block-background-fill: #0c2139;
--button-secondary-background-fill: #0c2139;
--color-accent-soft: #1c3f6f;
}
/* Общие стили для интерфейса */
.gradio-container {
max-width: 1200px !important;
margin: auto !important;
padding: 20px !important;
background-color: var(--background-color) !important;
border-radius: 15px !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
}
"""
demo = gr.ChatInterface(
fn=chatbot.predict,
title="🏠 Я умный ассистент по дизайну интерьера",
description="💬 Задайте вопрос, и я помогу вам подобрать товары для интерьера и отвечу на ваши запросы по дизайну.",
examples=[
"Мне нужен диван в стиле минимализм для гостиной",
"Посоветуй светильник для спальни в скандинавском стиле",
"Какие есть варианты обеденного стола до 30000 рублей?",
"Наполни мне корзину вещами для интерьера в стиле бохо"
],
css=custom_css
)
if __name__ == "__main__":
demo.launch(share=True)