gotheartem commited on
Commit
00b83d9
·
verified ·
1 Parent(s): ca9d6ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -25
app.py CHANGED
@@ -3,25 +3,35 @@ from pathlib import Path
3
 
4
  import aiosqlite
5
  import gradio as gr
 
6
  from openai import AsyncOpenAI
7
 
8
  # =========================
9
  # Конфигурация
10
  # =========================
11
 
12
- # Каталог и файл базы данных
13
  DB_DIR = Path("./data")
14
  DB_PATH = DB_DIR / "chats.db"
15
 
16
- # Названия 5 чатов
17
  CHAT_IDS = ["chat1", "chat2", "chat3", "chat4", "chat5"]
18
 
 
 
 
 
 
 
 
 
19
 
20
  def ensure_db_dir():
21
  DB_DIR.mkdir(parents=True, exist_ok=True)
22
 
23
 
24
- # Предустановленные учетные данные
 
 
 
25
  authorized_users_str = os.getenv("AUTHORIZED_USERS")
26
  if not authorized_users_str:
27
  raise ValueError("Переменная окружения AUTHORIZED_USERS не установлена")
@@ -33,14 +43,84 @@ if not proctor_links:
33
  PROCTOR_LINKS = dict(user.split(":", 1) for user in proctor_links.split(","))
34
 
35
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "Ты полезный ассистент.")
 
36
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
37
  if not OPENAI_API_KEY:
38
  raise ValueError("Переменная окружения OPENAI_API_KEY не установлена")
39
 
40
- # Инициализация AsyncOpenAI клиента один раз
 
 
 
 
41
  client = AsyncOpenAI(api_key=OPENAI_API_KEY)
42
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  # =========================
45
  # Работа с БД
46
  # =========================
@@ -92,13 +172,16 @@ async def load_history(chat_id, username):
92
  async def clear_history(chat_id, username):
93
  ensure_db_dir()
94
  async with aiosqlite.connect(DB_PATH) as db:
95
- await db.execute(f"DELETE FROM {chat_id} WHERE username = ?", (username,))
 
 
 
96
  await db.commit()
97
  return []
98
 
99
 
100
  # =========================
101
- # Основная логика чата
102
  # =========================
103
 
104
  async def send_message(message, history, username, chat_id):
@@ -116,10 +199,8 @@ async def send_message(message, history, username, chat_id):
116
  history = history or []
117
  clean_message = message.strip()
118
 
119
- # Сохраняем сообщение пользователя
120
  await save_message(chat_id, username, "user", clean_message)
121
 
122
- # Спец-команда
123
  if clean_message.lower() == "proctorlink":
124
  if username in PROCTOR_LINKS:
125
  assistant_response = PROCTOR_LINKS[username]
@@ -134,17 +215,20 @@ async def send_message(message, history, username, chat_id):
134
  ]
135
  return "", new_history
136
 
137
- # Формируем сообщения для модели с учетом истории
138
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
139
- messages.extend(history)
140
- messages.append({"role": "user", "content": clean_message})
 
 
 
141
 
142
  try:
143
  response = await client.chat.completions.create(
144
- model="gpt-4o",
145
  messages=messages,
146
  temperature=0.7,
147
- max_tokens=4000,
148
  timeout=120,
149
  )
150
  assistant_response = response.choices[0].message.content or ""
@@ -152,7 +236,6 @@ async def send_message(message, history, username, chat_id):
152
  print(f"OpenAI API error: {e}")
153
  assistant_response = "Error: Could not connect to the AI service."
154
 
155
- # Сохраняем ответ ассистента
156
  await save_message(chat_id, username, "assistant", assistant_response)
157
 
158
  new_history = history + [
@@ -262,34 +345,34 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
262
  load_history_btn = gr.Button("Загрузить историю", elem_id="load_history_btn")
263
  clear_history_btn = gr.Button("Очистить историю", elem_id="clear_history_btn")
264
 
265
- # Инициализация базы данных при запуске приложения
266
  demo.load(init_db, inputs=None, outputs=None)
267
-
268
- # Установка имени пользователя
269
  demo.load(set_username, inputs=None, outputs=username_state)
270
 
271
- # Обновление выбранного чата
272
  chat_selector.change(update_chat_id, inputs=chat_selector, outputs=chat_id_state)
273
 
274
- # Автозагрузка истории
275
- chat_id_state.change(load_chat_history, inputs=[username_state, chat_id_state], outputs=chatbot)
276
- username_state.change(load_chat_history, inputs=[username_state, chat_id_state], outputs=chatbot)
 
 
 
 
 
 
 
277
 
278
- # Ручная загрузка истории
279
  load_history_btn.click(
280
  load_chat_history,
281
  inputs=[username_state, chat_id_state],
282
  outputs=chatbot,
283
  )
284
 
285
- # Очистка истории
286
  clear_history_btn.click(
287
  handle_clear_history,
288
  inputs=[username_state, chat_id_state],
289
  outputs=chatbot,
290
  )
291
 
292
- # Отправка сообщения
293
  submit_btn.click(
294
  send_message,
295
  inputs=[msg, chatbot, username_state, chat_id_state],
 
3
 
4
  import aiosqlite
5
  import gradio as gr
6
+ import tiktoken
7
  from openai import AsyncOpenAI
8
 
9
  # =========================
10
  # Конфигурация
11
  # =========================
12
 
 
13
  DB_DIR = Path("./data")
14
  DB_PATH = DB_DIR / "chats.db"
15
 
 
16
  CHAT_IDS = ["chat1", "chat2", "chat3", "chat4", "chat5"]
17
 
18
+ # Ограничение входного контекста
19
+ INPUT_TOKEN_LIMIT = 4096
20
+
21
+ # Ограничение ответа модели
22
+ OUTPUT_TOKEN_LIMIT = 1024
23
+
24
+ MODEL_NAME = "gpt-4o-mini"
25
+
26
 
27
  def ensure_db_dir():
28
  DB_DIR.mkdir(parents=True, exist_ok=True)
29
 
30
 
31
+ # =========================
32
+ # Переменные окружения
33
+ # =========================
34
+
35
  authorized_users_str = os.getenv("AUTHORIZED_USERS")
36
  if not authorized_users_str:
37
  raise ValueError("Переменная окружения AUTHORIZED_USERS не установлена")
 
43
  PROCTOR_LINKS = dict(user.split(":", 1) for user in proctor_links.split(","))
44
 
45
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "Ты полезный ассистент.")
46
+
47
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
48
  if not OPENAI_API_KEY:
49
  raise ValueError("Переменная окружения OPENAI_API_KEY не установлена")
50
 
51
+
52
+ # =========================
53
+ # OpenAI client
54
+ # =========================
55
+
56
  client = AsyncOpenAI(api_key=OPENAI_API_KEY)
57
 
58
 
59
+ # =========================
60
+ # Подсчет токенов и обрезка истории
61
+ # =========================
62
+
63
+ def get_encoder(model_name: str = MODEL_NAME):
64
+ try:
65
+ return tiktoken.encoding_for_model(model_name)
66
+ except Exception:
67
+ return tiktoken.get_encoding("cl100k_base")
68
+
69
+
70
+ def count_message_tokens(messages, model_name: str = MODEL_NAME) -> int:
71
+ enc = get_encoder(model_name)
72
+ total = 0
73
+
74
+ # Приблизительный расчет для chat messages
75
+ for msg in messages:
76
+ total += 4
77
+ total += len(enc.encode(msg.get("role", "")))
78
+ total += len(enc.encode(msg.get("content", "")))
79
+ total += 2
80
+
81
+ return total
82
+
83
+
84
+ def trim_messages_to_limit(
85
+ system_prompt: str,
86
+ history: list,
87
+ new_user_message: str,
88
+ model_name: str = MODEL_NAME,
89
+ input_limit: int = INPUT_TOKEN_LIMIT,
90
+ ):
91
+ base_messages = [{"role": "system", "content": system_prompt}]
92
+ candidate_history = history[:] if history else []
93
+
94
+ final_messages = base_messages + candidate_history + [
95
+ {"role": "user", "content": new_user_message}
96
+ ]
97
+
98
+ while len(final_messages) > 2 and count_message_tokens(final_messages, model_name) > input_limit:
99
+ if candidate_history:
100
+ candidate_history.pop(0)
101
+ else:
102
+ break
103
+
104
+ final_messages = base_messages + candidate_history + [
105
+ {"role": "user", "content": new_user_message}
106
+ ]
107
+
108
+ # Если даже без истории сообщение слишком длинное — режем само сообщение
109
+ if count_message_tokens(final_messages, model_name) > input_limit:
110
+ enc = get_encoder(model_name)
111
+
112
+ system_only_tokens = count_message_tokens(base_messages, model_name)
113
+ allowed_for_user = max(1, input_limit - system_only_tokens - 20)
114
+
115
+ trimmed_user_message = enc.decode(enc.encode(new_user_message)[:allowed_for_user])
116
+
117
+ final_messages = base_messages + [
118
+ {"role": "user", "content": trimmed_user_message}
119
+ ]
120
+
121
+ return final_messages
122
+
123
+
124
  # =========================
125
  # Работа с БД
126
  # =========================
 
172
  async def clear_history(chat_id, username):
173
  ensure_db_dir()
174
  async with aiosqlite.connect(DB_PATH) as db:
175
+ await db.execute(
176
+ f"DELETE FROM {chat_id} WHERE username = ?",
177
+ (username,),
178
+ )
179
  await db.commit()
180
  return []
181
 
182
 
183
  # =========================
184
+ # Основная логика
185
  # =========================
186
 
187
  async def send_message(message, history, username, chat_id):
 
199
  history = history or []
200
  clean_message = message.strip()
201
 
 
202
  await save_message(chat_id, username, "user", clean_message)
203
 
 
204
  if clean_message.lower() == "proctorlink":
205
  if username in PROCTOR_LINKS:
206
  assistant_response = PROCTOR_LINKS[username]
 
215
  ]
216
  return "", new_history
217
 
218
+ messages = trim_messages_to_limit(
219
+ system_prompt=SYSTEM_PROMPT,
220
+ history=history,
221
+ new_user_message=clean_message,
222
+ model_name=MODEL_NAME,
223
+ input_limit=INPUT_TOKEN_LIMIT,
224
+ )
225
 
226
  try:
227
  response = await client.chat.completions.create(
228
+ model=MODEL_NAME,
229
  messages=messages,
230
  temperature=0.7,
231
+ max_tokens=OUTPUT_TOKEN_LIMIT,
232
  timeout=120,
233
  )
234
  assistant_response = response.choices[0].message.content or ""
 
236
  print(f"OpenAI API error: {e}")
237
  assistant_response = "Error: Could not connect to the AI service."
238
 
 
239
  await save_message(chat_id, username, "assistant", assistant_response)
240
 
241
  new_history = history + [
 
345
  load_history_btn = gr.Button("Загрузить историю", elem_id="load_history_btn")
346
  clear_history_btn = gr.Button("Очистить историю", elem_id="clear_history_btn")
347
 
 
348
  demo.load(init_db, inputs=None, outputs=None)
 
 
349
  demo.load(set_username, inputs=None, outputs=username_state)
350
 
 
351
  chat_selector.change(update_chat_id, inputs=chat_selector, outputs=chat_id_state)
352
 
353
+ chat_id_state.change(
354
+ load_chat_history,
355
+ inputs=[username_state, chat_id_state],
356
+ outputs=chatbot,
357
+ )
358
+ username_state.change(
359
+ load_chat_history,
360
+ inputs=[username_state, chat_id_state],
361
+ outputs=chatbot,
362
+ )
363
 
 
364
  load_history_btn.click(
365
  load_chat_history,
366
  inputs=[username_state, chat_id_state],
367
  outputs=chatbot,
368
  )
369
 
 
370
  clear_history_btn.click(
371
  handle_clear_history,
372
  inputs=[username_state, chat_id_state],
373
  outputs=chatbot,
374
  )
375
 
 
376
  submit_btn.click(
377
  send_message,
378
  inputs=[msg, chatbot, username_state, chat_id_state],