Skip to content

Commit e71727b

Browse files
committed
fix format problems ruff
1 parent 9a78bc3 commit e71727b

17 files changed

+415
-340
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ export FOLDER_ID="x1ue823jv1ws48cloudy1"
44
export API_KEY="QWJEJ48m6LfUEggPbmocHmW00JJgpZNetSv_NaZZaret"
55
export EMBEDDING_API="https://llm.api.cloud.yandex.net:443/foundationModels/v1/textEmbedding"
66
export YANDEXGPT_API="https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
7-
export COLLECTION_NAME="profkom_documents_yandex_emb_and_tiktoken_ru"
7+
export COLLECTION_NAME="default_collection"
88
export UNIQUE_BOT_ID="41823841384:FF1I2RagnarekC4MAyBEF9kWvAAqaaabWRRN1oOOPr"
99
export DEFAULT_PROMPT_FILE="default_prompt.txt"
1010
export DEFAULT_DIR_PROMPT="./prompts"
1111
export MAX_HISTORY_USER_LENGTH=10
1212
export HISTORY_USER_TTL_DAYS=7
13+
export TOP_K_DOCUMENTS=5
14+
export MAX_RAG_DOCUMENTS=20
1315
export MAX_FASTAPI_THREADS=10
16+
export TIME_SLEEP_RATE_EMBEDDER=0.01
1417
export CHROMA_HOST="127.0.0.1"
1518
export REDIS_HOST="127.0.0.1"
1619
export CHROMA_PORT="8000"

.env.prod.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ FOLDER_ID="x1ue823jv1ws48cloudy1"
22
API_KEY="QWJEJ48m6LfUEggPbmocHmW00JJgpZNetSv_NaZZaret"
33
EMBEDDING_API="https://llm.api.cloud.yandex.net:443/foundationModels/v1/textEmbedding"
44
YANDEXGPT_API="https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
5-
COLLECTION_NAME="profkom_documents_yandex_emb_and_tiktoken_ru"
5+
COLLECTION_NAME="default_collection"
66
UNIQUE_BOT_ID="41823841384:FF1I2RagnarekC4MAyBEF9kWvAAqaaabWRRN1oOOPr"
77
DEFAULT_PROMPT_FILE="default_prompt.txt"
88
DEFAULT_DIR_PROMPT="./prompts"
99
MAX_HISTORY_USER_LENGTH=10
1010
HISTORY_USER_TTL_DAYS=7
11+
TOP_K_DOCUMENTS=5
12+
MAX_RAG_DOCUMENTS=20
1113
MAX_FASTAPI_THREADS=10
14+
TIME_SLEEP_RATE_EMBEDDER=0.01
1215
CHROMA_HOST="chromadb"
1316
REDIS_HOST="redis"
1417
CHROMA_PORT="8000"

nginx/nginx.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ http {
3333
# Ban status mapping - more selective
3434
map $status $is_banned_status {
3535
default 0;
36+
400 1;
37+
401 1;
38+
402 1;
39+
403 1;
40+
404 1;
41+
405 1;
42+
406 1;
43+
407 1;
44+
408 1;
45+
409 1;
46+
422 1;
47+
423 1;
48+
424 1;
49+
425 1;
50+
426 1;
51+
428 1;
3652
429 1;
3753
}
3854

prompts/assistant_prompt.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55

66
Твои ограничения и возможности:
77

8-
1. Разрешено модифицировать, перефразировать и дополнять вопрос;
9-
2. Разрешено использовать историю диалога с пользователем для того, чтобы узнать чего он хочет.
8+
1. Разрешено перефразировать по стилю и дополнять вопрос исходя из истории поиска;
9+
2. Разрешено использовать историю диалога с пользователем для того, чтобы узнать какой вопрос он задает.
10+
3. Нельзя полностью изменять текущий вопрос пользователя!

prompts/default_prompt.txt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
1. Если ты не знаешь ответ, то ты не придумываешь ответ!
88
2. Ты обязан следовать формату ответа на вопрос пользователя.
9-
3. Если считаешь, что вопрос пользователя был неконкретным или общим, то помоги ему правильно сформировать вопрос.
10-
4. Если информации недостаточно для ответа, то посмотри на историю последнего диалога с пользователем.
11-
5. Ответ давай наиболее коротко и по делу, чтобы с первого ответа пользователю стало понятно.
9+
3. Если информации недостаточно для ответа, то посмотри на историю последнего диалога с пользователем в разделе <История диалога с пользователем>.
10+
4. Ответ давай наиболее коротко и по делу. Старайся информацию выдавать в стиле профессионального юрист консультанта.
11+
5. Обязательно помогай пользователю найти информацию на свой вопрос. Рекомендуй ему дальнейшие шаги, чтобы найти ответ на вопрос.
1212

1313
Следуй следующему формату сообщений, далее ФОРМАТ:
1414

@@ -18,8 +18,10 @@
1818
Ответ: "Тут содержится ответа на вопрос пользователя. А тут дается информация с приложенной ссылкой [0]".
1919
(ОБЯЗАТЕЛЬНО ДАЙ ОТВЕТ В ЭТОМ ФОРМАТЕ)
2020

21-
Источник(и):
22-
№0 (номер порядковый из RAG) - информация получена из файла TEXT,
23-
относится к пункту документа TEXT (указана URL, название файла и\или информация из <Источник>),
24-
название документа TEXT
21+
Источник(и): (Если источник не существует, то не надо придумывать их!)
22+
23+
№0 (номер порядковый из RAG если есть что-то для ответа):
24+
- название документа (как называется сам документ к которому относится данная информация);
25+
- относится к пункту документа (укажи пункт в котором встречается данный текст => указана URL, название файла и\или информация из <Источник>);
26+
- информация получена из файла (укажи название файла);
2527
-------------------------</ФОРМАТ>-----------------------------------------

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup, find_packages
22

3-
__version__ = "0.1.0"
3+
__version__ = "0.2.1"
44

55
# Read requirements from requirements.txt
66
with open("requirements.txt") as f:

src/UnionChatBot/CoreLogic.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import json
2+
import os
3+
from typing import Optional
4+
5+
import requests
6+
7+
from UnionChatBot.utils.BasicManager import BasicManager
8+
from UnionChatBot.utils.EmbeddingAPI import MyEmbeddingFunction
9+
from UnionChatBot.utils.ChromaAdapter import ChromaAdapter
10+
from UnionChatBot.utils.RedisAdapters import SemanticRedisCache
11+
from UnionChatBot.utils.ChatHistoryManager import ChatHistoryManager
12+
from UnionChatBot.utils.QueryRewriteManager import QueryRewriteManager
13+
14+
15+
class CoreQueryProcessor(BasicManager):
16+
"""Центральный класс позволяющий реализовать логику работы чат-бота.
17+
18+
Args:
19+
embedding_function: объект класса отвечающий за векторизацию текста.
20+
chroma_adapter: объект класса отвечающий за взаимодействие с векторной БД.
21+
redis_cache: объект класса отвечающий за взаимодействие с горячей БД Redis.
22+
chat_manager: объект класса отвечающий за контроль истории пользователя при общении с чат-ботом.
23+
"""
24+
25+
core_prompt_file = os.getenv("DEFAULT_PROMPT_FILE", "default_prompt.txt")
26+
core_prompt_dir = os.getenv("DEFAULT_DIR_PROMPT", "./prompts")
27+
28+
def __init__(
29+
self,
30+
temperature: float = 0.3,
31+
stream: bool = False,
32+
maxTokens: int = 2000,
33+
model_name: str = "deepseek-r1-distill-qwen-32b",
34+
embedding_function: MyEmbeddingFunction = None,
35+
chroma_adapter: ChromaAdapter = None,
36+
redis_cache: SemanticRedisCache = None,
37+
chat_manager: ChatHistoryManager = None,
38+
query_rewriter: Optional[QueryRewriteManager] = None,
39+
**kwargs,
40+
):
41+
super().__init__(
42+
model_name=model_name,
43+
temperature=temperature,
44+
stream=stream,
45+
maxTokens=maxTokens,
46+
**kwargs,
47+
)
48+
49+
self.embedding_function = embedding_function
50+
self.redis_cache = redis_cache
51+
self.chroma_adapter = chroma_adapter
52+
self.chat_manager = chat_manager
53+
self.query_rewriter = query_rewriter
54+
55+
def modify_system_prompt(self, prompt: str, data: dict, user_id: str) -> str:
56+
"""Модифицируем системный промт исходя из ответов из базы данных.
57+
58+
Args:
59+
prompt: системый промт по умолчанию.
60+
data: словарь с релевантной информацией из БД.
61+
user_id: уникальный индетефикатор пользователя.
62+
63+
Return:
64+
Модифицированный системный промт исходя из дополнительной информации из БД и истории диалога.
65+
"""
66+
history_data = self.chat_manager.get_formatted_history(user_id=user_id)
67+
prompt += "<RAG>"
68+
context = (
69+
" ".join(
70+
[
71+
"№"
72+
+ str(idx)
73+
+ " <Информация>: "
74+
+ info[0]
75+
+ " "
76+
+ "<Источник>: "
77+
+ info[1].get(list(info[1].keys())[0])
78+
+ " </Источник> <Файл> "
79+
+ list(info[1].keys())[0]
80+
+ "</Файл>"
81+
+ "</Информация> \n"
82+
for idx, info in enumerate(
83+
zip(data.get("documents"), data.get("metadatas"))
84+
)
85+
]
86+
)
87+
+ "</RAG>"
88+
)
89+
prompt += " " + context + history_data
90+
return prompt
91+
92+
def ask(self, query: str, collection_name: str, user_id: str) -> str:
93+
"""Инициализация диалога с чат-ботом.
94+
95+
Args:
96+
query: вопрос пользователя & сообщение.
97+
collection_name: название коллекции к которой необходимо обратиться в ChromaDB.
98+
user_id: уникальный идентификатор пользователя.
99+
100+
Return:
101+
Текстовый ответ модели для пользователя.
102+
"""
103+
system_prompt = self.read_prompt(
104+
prompt_file=self.core_prompt_file, prompt_dir=self.core_prompt_dir
105+
)
106+
107+
if self.query_rewriter:
108+
query, status = self.query_rewriter.rewrite(query=query, user_id=user_id)
109+
if status != 200:
110+
return query
111+
112+
query_embedding = self.embedding_function(query)
113+
114+
cached = self.redis_cache.get(query, query_embedding)
115+
if cached:
116+
self.chat_manager.add_message_to_history(
117+
user_id=user_id, message=cached["response"]
118+
)
119+
return cached["response"]
120+
121+
data = self.chroma_adapter.get_info(
122+
query=query, collection_name=collection_name
123+
)
124+
new_prompt = self.modify_system_prompt(
125+
prompt=system_prompt, data=data, user_id=user_id
126+
)
127+
response = requests.post(
128+
url=self.url,
129+
headers=self.setup_header(),
130+
data=self.setup_data(text=query, prompt=new_prompt),
131+
)
132+
133+
if response.status_code == 200:
134+
dict_response = json.loads(response.content)
135+
answer = (
136+
dict_response.get("result")
137+
.get("alternatives")[0]
138+
.get("message")
139+
.get("text")
140+
)
141+
self.redis_cache.set(query, query_embedding, answer)
142+
self.chat_manager.add_message_to_history(user_id=user_id, message=answer)
143+
else:
144+
answer = (
145+
f"Код ответа {response.status_code}. Попробуйте задать вопрос позднее."
146+
)
147+
return answer
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import json
2+
import os
3+
4+
5+
class BasicManager:
6+
"""Базовые возможности менеджера запросов к YandexGPTAPI.
7+
8+
Args:
9+
temperature: температура с которой генерируются ответы модели.
10+
stream: необходимость возвращать ответ посимвольно.
11+
maxTokens: ограничение на кол-во токенов для модели суммарно генерация + ответ.
12+
folder_id: секрет принадлежащий сервисному аккаунту Yandex GPT API.
13+
api_key: секрет принадлежащий сервисному аккаунту Yandex GPT API.
14+
url: ссылка на YandexGPTAPI inference.
15+
model_name: модель используемая для генерации ответа.
16+
"""
17+
18+
url = os.getenv(
19+
"YANDEXGPT_API",
20+
"https://llm.api.cloud.yandex.net/foundationModels/v1/completion",
21+
)
22+
folder_id = os.getenv("FOLDER_ID", None)
23+
api_key = os.getenv("API_KEY", None)
24+
25+
def __init__(
26+
self,
27+
model_name: str,
28+
maxTokens: str = "8000",
29+
stream: bool = False,
30+
temperature: float = 0.3,
31+
**kwargs,
32+
):
33+
self.stream = stream
34+
self.temperature = temperature
35+
self.model_name = model_name
36+
self.maxTokens = maxTokens
37+
38+
if self.folder_id is None or self.api_key is None:
39+
raise ValueError(
40+
"FOLDER_ID or API_KEY hasn`t been defined at ENV! This is important parameters for YandexCloud API!"
41+
)
42+
43+
if self.url is None:
44+
raise ValueError(
45+
"YANDEXGPT_API url hasn`t been defined at ENV! How you are going to inference at all???"
46+
)
47+
48+
if maxTokens >= 8001:
49+
raise Warning("It is not recommended to set more than 8000 tokens!")
50+
51+
if maxTokens >= 32000:
52+
raise Warning(
53+
"You set limited maxTokens rate based on YandexAPI docs at 2025!"
54+
)
55+
56+
def setup_header(self) -> dict:
57+
"""Генерируем классический Header запроса.
58+
59+
Return:
60+
Требуемого вида JSON объект в виде словаря, для корректной аунтификации на endpoint.
61+
"""
62+
return {
63+
"Content-Type": "application/json",
64+
"Authorization": "Api-Key " + self.api_key,
65+
"x-folder-id": self.folder_id,
66+
"x-data-logging-enabled": "false",
67+
}
68+
69+
def setup_data(self, text: str, prompt: str) -> json.dumps:
70+
"""Генерируем полное тело запроса для последующей генерации ответа модели.
71+
72+
Args:
73+
text: Запрос пользователя (может быть в сыром виде);
74+
prompt: Дополнительная информация для генерации правильного ответа моделью.
75+
76+
Return:
77+
Полное тело запроса для Yandex GPT endpoint.
78+
"""
79+
return json.dumps(
80+
{
81+
"modelUri": f"gpt://{self.folder_id}/{self.model_name}",
82+
"completionOptions": {
83+
"stream": self.stream,
84+
"temperature": self.temperature,
85+
"maxTokens": str(self.maxTokens),
86+
},
87+
"messages": [
88+
{"role": "system", "text": prompt},
89+
{"role": "user", "text": text},
90+
],
91+
}
92+
)
93+
94+
@staticmethod
95+
def read_prompt(prompt_file: str, prompt_dir: str) -> str:
96+
with open(prompt_dir + "/" + prompt_file, "r", encoding="utf-8") as file:
97+
return file.read()

0 commit comments

Comments
 (0)