Rulga commited on
Commit
0f93e9d
·
1 Parent(s): 60cfc29

Initialize project structure and add configuration for knowledge base and document loading

Browse files
README.md CHANGED
@@ -9,4 +9,36 @@ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  pinned: false
10
  ---
11
 
12
+ An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
13
+
14
+ # Status Law Assistant
15
+
16
+ Чат-бот на базе Hugging Face и LangChain для юридической консультации на основе информации с сайта компании Status Law.
17
+
18
+ ## 📝 Описание
19
+
20
+ Status Law Assistant — это интеллектуальный чат-бот, который отвечает на вопросы пользователей о юридических услугах компании Status Law. Бот использует технологию RAG (Retrieval-Augmented Generation), чтобы находить релевантную информацию в базе знаний, созданной на основе содержимого официального сайта компании, и генерировать на её основе ответы с помощью языковой модели.
21
+
22
+ ## ✨ Возможности
23
+
24
+ - Автоматическое создание и обновление базы знаний на основе контента сайта status.law
25
+ - Поиск релевантной информации для ответа на вопросы пользователей
26
+ - Генерация ответов с использованием контекстно-ориентированного подхода
27
+ - Поддержка многоязычных запросов (отвечает на языке вопроса)
28
+ - Настраиваемые параметры генерации текста (температура, количество токенов и т.д.)
29
+
30
+ ## 🚀 Технологии
31
+
32
+ - **LangChain**: для создания цепочек обработки запросов и управления базой знаний
33
+ - **Hugging Face**: для доступа к языковым моделям и хостинга приложения
34
+ - **FAISS**: для эффективного векторного поиска
35
+ - **Gradio**: для создания пользовательского интерфейса
36
+ - **BeautifulSoup**: для извлечения информации с веб-страниц
37
+
38
+ ## 🏗️ Структура проекта
39
+
40
+ - `app.py`: основной файл приложения, в котором определен интерфейс и логика обработки запросов
41
+ - `config/`: директория с конфигурационными файлами
42
+ - `src/`: директория с исходным кодом
43
+ - `knowledge_base/`: модуль для работы с базой знаний
44
+ - `models/`: модуль для работы с моделями
app.py CHANGED
@@ -1,32 +1,70 @@
1
  import gradio as gr
 
2
  from huggingface_hub import InferenceClient
 
 
 
3
 
4
- """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def respond(
11
  message,
12
- history: list[tuple[str, str]],
 
13
  system_message,
14
  max_tokens,
15
  temperature,
16
  top_p,
17
  ):
18
- messages = [{"role": "system", "content": system_message}]
19
-
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
25
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  messages.append({"role": "user", "content": message})
27
-
 
28
  response = ""
29
-
30
  for message in client.chat_completion(
31
  messages,
32
  max_tokens=max_tokens,
@@ -35,30 +73,89 @@ def respond(
35
  top_p=top_p,
36
  ):
37
  token = message.choices[0].delta.content
 
 
 
38
 
39
- response += token
40
- yield response
41
-
42
-
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- demo = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
- )
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
 
63
  if __name__ == "__main__":
64
- demo.launch()
 
 
 
 
 
1
  import gradio as gr
2
+ import os
3
  from huggingface_hub import InferenceClient
4
+ from config.constants import DEFAULT_SYSTEM_MESSAGE
5
+ from config.settings import DEFAULT_MODEL
6
+ from src.knowledge_base.vector_store import create_vector_store, load_vector_store
7
 
8
+ # Создаем клиент для инференса
9
+ client = InferenceClient(DEFAULT_MODEL)
 
 
10
 
11
+ # Состояние для хранения контекста
12
+ context_store = {}
13
+
14
+ def get_context(message, conversation_id):
15
+ """Получение контекста из базы знаний"""
16
+ vector_store = load_vector_store()
17
+ if vector_store is None:
18
+ return "База знаний не найдена. Пожалуйста, создайте её сначала."
19
+
20
+ try:
21
+ # Извлечение контекста
22
+ context_docs = vector_store.similarity_search(message, k=3)
23
+ context_text = "\n\n".join([f"Из {doc.metadata.get('source', 'неизвестно')}: {doc.page_content}" for doc in context_docs])
24
+
25
+ # Сохраняем контекст для этого разговора
26
+ context_store[conversation_id] = context_text
27
+
28
+ return context_text
29
+ except Exception as e:
30
+ print(f"Ошибка при получении контекста: {str(e)}")
31
+ return ""
32
 
33
  def respond(
34
  message,
35
+ history,
36
+ conversation_id,
37
  system_message,
38
  max_tokens,
39
  temperature,
40
  top_p,
41
  ):
42
+ # Если это новый разговор, создаем ID
43
+ if not conversation_id:
44
+ import uuid
45
+ conversation_id = str(uuid.uuid4())
46
+
47
+ # Получаем контекст из базы знаний
48
+ context = get_context(message, conversation_id)
49
+
50
+ # Формируем полную системную инструкцию с контекстом
51
+ full_system_message = system_message
52
+ if context:
53
+ full_system_message += f"\n\nКонтекст для ответа:\n{context}"
54
+
55
+ # Формируем сообщения для LLM
56
+ messages = [{"role": "system", "content": full_system_message}]
57
+
58
+ # Преобразуем историю в формат для API
59
+ for user_msg, bot_msg in history:
60
+ messages.append({"role": "user", "content": user_msg})
61
+ messages.append({"role": "assistant", "content": bot_msg})
62
+
63
+ # Добавляем текущее сообщение пользователя
64
  messages.append({"role": "user", "content": message})
65
+
66
+ # Отправляем запрос к API и стримим ответ
67
  response = ""
 
68
  for message in client.chat_completion(
69
  messages,
70
  max_tokens=max_tokens,
 
73
  top_p=top_p,
74
  ):
75
  token = message.choices[0].delta.content
76
+ if token:
77
+ response += token
78
+ yield response, conversation_id
79
 
80
+ def build_kb():
81
+ """Функция для создания базы знаний"""
82
+ try:
83
+ success, message = create_vector_store()
84
+ return message
85
+ except Exception as e:
86
+ return f"Ошибка при создании базы знаний: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ # Создаем интерфейс
89
+ with gr.Blocks() as demo:
90
+ gr.Markdown("# 🤖 Status Law Assistant")
91
+
92
+ conversation_id = gr.State(None)
93
+
94
+ with gr.Row():
95
+ with gr.Column(scale=3):
96
+ chatbot = gr.Chatbot(label="Чат")
97
+
98
+ with gr.Row():
99
+ msg = gr.Textbox(
100
+ label="Ваш вопрос",
101
+ placeholder="Введите ваш вопрос...",
102
+ scale=4
103
+ )
104
+ submit_btn = gr.Button("Отправить", variant="primary")
105
+
106
+ with gr.Column(scale=1):
107
+ gr.Markdown("### Управление базой знаний")
108
+ build_kb_btn = gr.Button("Создать/обновить базу знаний", variant="primary")
109
+ kb_status = gr.Textbox(label="Статус базы знаний", interactive=False)
110
+
111
+ gr.Markdown("### Настройки чата")
112
+ system_message = gr.Textbox(
113
+ label="Системное сообщение",
114
+ value=DEFAULT_SYSTEM_MESSAGE,
115
+ lines=5
116
+ )
117
+ max_tokens = gr.Slider(
118
+ minimum=1,
119
+ maximum=2048,
120
+ value=512,
121
+ step=1,
122
+ label="Максимальное количество токенов"
123
+ )
124
+ temperature = gr.Slider(
125
+ minimum=0.1,
126
+ maximum=2.0,
127
+ value=0.7,
128
+ step=0.1,
129
+ label="Температура"
130
+ )
131
+ top_p = gr.Slider(
132
+ minimum=0.1,
133
+ maximum=1.0,
134
+ value=0.95,
135
+ step=0.05,
136
+ label="Top-p (nucleus sampling)"
137
+ )
138
+
139
+ clear_btn = gr.Button("Очистить историю чата")
140
+
141
+ # Обработчики событий
142
+ msg.submit(
143
+ respond,
144
+ [msg, chatbot, conversation_id, system_message, max_tokens, temperature, top_p],
145
+ [chatbot, conversation_id]
146
+ )
147
+ submit_btn.click(
148
+ respond,
149
+ [msg, chatbot, conversation_id, system_message, max_tokens, temperature, top_p],
150
+ [chatbot, conversation_id]
151
+ )
152
+ build_kb_btn.click(build_kb, None, kb_status)
153
+ clear_btn.click(lambda: ([], None), None, [chatbot, conversation_id])
154
 
155
+ # Запускаем приложение
156
  if __name__ == "__main__":
157
+ # Проверяем наличие базы знаний
158
+ if not os.path.exists(os.path.join("data", "vector_store", "index.faiss")):
159
+ print("База знаний не найдена. Создайте её через интерфейс.")
160
+
161
+ demo.launch()
config/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
config/constants.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # URLs для создания базы знаний
2
+ URLS = [
3
+ "https://status.law",
4
+ "https://status.law/about",
5
+ "https://status.law/careers",
6
+ "https://status.law/tariffs-for-services-of-protection-against-extradition",
7
+ "https://status.law/challenging-sanctions",
8
+ "https://status.law/law-firm-contact-legal-protection",
9
+ "https://status.law/cross-border-banking-legal-issues",
10
+ "https://status.law/extradition-defense",
11
+ "https://status.law/international-prosecution-protection",
12
+ "https://status.law/interpol-red-notice-removal",
13
+ "https://status.law/practice-areas",
14
+ "https://status.law/reputation-protection",
15
+ "https://status.law/faq"
16
+ ]
17
+
18
+ # Настройки для чанкирования текста
19
+ CHUNK_SIZE = 500
20
+ CHUNK_OVERLAP = 100
21
+
22
+ # Шаблон системного сообщения
23
+ DEFAULT_SYSTEM_MESSAGE = """
24
+ You are a helpful and polite legal assistant at Status Law.
25
+ You answer in the language in which the question was asked.
26
+ Answer the question based on the context provided.
27
+ If you cannot answer based on the context, say so politely and offer to contact Status Law directly via the following channels:
28
+ - For all users: +32465594521 (landline phone).
29
+ - For English and Swedish speakers only: +46728495129 (available on WhatsApp, Telegram, Signal, IMO).
30
+ - Provide a link to the contact form: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
31
+ If the user has questions about specific services and their costs, suggest they visit the page https://status.law/tariffs-for-services-of-protection-against-extradition-and-international-prosecution/ for detailed information.
32
+
33
+ Ask the user additional questions to understand which service to recommend and provide an estimated cost. For example, clarify their situation and needs to suggest the most appropriate options.
34
+
35
+ Also, offer free consultations if they are available and suitable for the user's request.
36
+ Answer professionally but in a friendly manner.
37
+
38
+ Example:
39
+ Q: How can I challenge the sanctions?
40
+ A: To challenge the sanctions, you should consult with our legal team, who specialize in this area. Please contact us directly for detailed advice. You can fill out our contact form here: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
41
+
42
+ Context: {context}
43
+ Question: {question}
44
+
45
+ Response Guidelines:
46
+ 1. Answer in the user's language
47
+ 2. Cite sources when possible
48
+ 3. Offer contact options if unsure
49
+ """
config/settings.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Загрузка переменных окружения
5
+ load_dotenv()
6
+
7
+ # Пути к директориям
8
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
9
+ VECTOR_STORE_PATH = os.path.join(BASE_DIR, "data", "vector_store")
10
+
11
+ # Настройки моделей
12
+ EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
13
+ DEFAULT_MODEL = "HuggingFaceH4/zephyr-7b-beta" # Модель по умолчанию из шаблона
requirements.txt CHANGED
@@ -1 +1,12 @@
1
- huggingface_hub==0.25.2
 
 
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub==0.25.2
2
+ gradio>=4.0.0
3
+ langchain>=0.1.0
4
+ langchain-community>=0.0.11
5
+ langchain-core>=0.1.10
6
+ langchain-text-splitters>=0.0.1
7
+ langchain-huggingface>=0.0.1
8
+ faiss-cpu>=1.7.4
9
+ sentence-transformers>=2.2.2
10
+ beautifulsoup4>=4.12.2
11
+ requests>=2.31.0
12
+ python-dotenv>=1.0.0
src/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
src/interface/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
src/knowledge_base/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
src/knowledge_base/loader.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ from langchain_community.document_loaders import WebBaseLoader
4
+ from langchain_core.documents import Document
5
+ from config.constants import URLS
6
+
7
+ def load_documents():
8
+ """Загрузка документов с веб-сайта"""
9
+ documents = []
10
+
11
+ headers = {
12
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
13
+ }
14
+
15
+ for url in URLS:
16
+ try:
17
+ loader = WebBaseLoader(
18
+ web_paths=[url],
19
+ header_template=headers
20
+ )
21
+ docs = loader.load()
22
+ if docs:
23
+ documents.extend(docs)
24
+ print(f"Загружено {url}: {len(docs)} документов")
25
+ except Exception as e:
26
+ print(f"Ошибка загрузки {url}: {str(e)}")
27
+
28
+ return documents
src/knowledge_base/vector_store.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
3
+ from langchain_community.vectorstores import FAISS
4
+ from langchain_huggingface import HuggingFaceEmbeddings
5
+ from src.knowledge_base.loader import load_documents
6
+ from config.settings import VECTOR_STORE_PATH, EMBEDDING_MODEL
7
+ from config.constants import CHUNK_SIZE, CHUNK_OVERLAP
8
+
9
+ def get_embeddings():
10
+ """Получение модели эмбеддингов"""
11
+ return HuggingFaceEmbeddings(
12
+ model_name=EMBEDDING_MODEL,
13
+ model_kwargs={'device': 'cpu'}
14
+ )
15
+
16
+ def create_vector_store():
17
+ """Создание или обновление векторного хранилища"""
18
+ # Загрузка документов
19
+ documents = load_documents()
20
+
21
+ if not documents:
22
+ return False, "Ошибка: документы не загружены"
23
+
24
+ # Разделение на чанки
25
+ text_splitter = RecursiveCharacterTextSplitter(
26
+ chunk_size=CHUNK_SIZE,
27
+ chunk_overlap=CHUNK_OVERLAP
28
+ )
29
+ chunks = text_splitter.split_documents(documents)
30
+
31
+ # Инициализация эмбеддингов
32
+ embeddings = get_embeddings()
33
+
34
+ # Создание векторного хранилища
35
+ vector_store = FAISS.from_documents(chunks, embeddings)
36
+
37
+ # Сохранение
38
+ os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
39
+ vector_store.save_local(folder_path=VECTOR_STORE_PATH)
40
+
41
+ return True, f"База знаний создана успешно! Загружено {len(documents)} документов, создано {len(chunks)} чанков."
42
+
43
+ def load_vector_store():
44
+ """Загрузка векторного хранилища"""
45
+ embeddings = get_embeddings()
46
+
47
+ if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
48
+ return None
49
+
50
+ try:
51
+ vector_store = FAISS.load_local(
52
+ VECTOR_STORE_PATH,
53
+ embeddings,
54
+ allow_dangerous_deserialization=True
55
+ )
56
+ return vector_store
57
+ except Exception as e:
58
+ print(f"Ошибка загрузки векторного хранилища: {str(e)}")
59
+ return None
src/models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+