muryshev commited on
Commit
ecf21a8
·
1 Parent(s): 527698c

Migration to deepinfra

Browse files
Files changed (8) hide show
  1. .gitignore +8 -0
  2. app.py +83 -168
  3. llm/common.py +69 -0
  4. llm/deepinfra_api.py +151 -0
  5. llm/prompts.py +98 -0
  6. llm/vllm_api-sync.py +375 -0
  7. llm/vllm_api.py +317 -0
  8. requirements +1 -2
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ *.bat
2
+ __pycache__
3
+ .cache
4
+ .local
5
+ .nv
6
+ *.bash_history
7
+ *.zip
8
+ .env
app.py CHANGED
@@ -1,14 +1,22 @@
1
  from flask import Flask, request, Response, jsonify
2
- from huggingface_hub import InferenceClient
3
  from flask_cors import CORS
4
  import json
5
- import requests
6
  import re
7
- import uuid
 
 
 
 
8
 
9
- client = InferenceClient("mistralai/Mixtral-8x7B-Instruct-v0.1")
 
10
 
11
- summary_prompt = '<s>[INST]Ты ассистент. Отвечаешь на русском языке. Сформируй краткое изложение следующего текста: {}[/INST]'
 
 
 
 
 
12
 
13
  def format_prompt(message, history):
14
  prompt = "<s>"
@@ -37,196 +45,134 @@ def split_text(text):
37
  result.append(current_chunk.strip())
38
  return result
39
 
40
- def call_api(prompt_text):
41
- url = "https://muryshev-mixtral-api-protocol.hf.space/completion?bypass_too_many_requests="+str(uuid.uuid4())
42
- payload = {"prompt": prompt_text, "temperature": 0, "seed": 42, "repeat_penalty": 1, "top_p": 0.95, "stream": False, "n_predict": 2000}
43
-
44
- try:
45
- response = requests.post(url, json=payload)
46
- response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
47
- result = response.json().get('content', '') # Extract the text result from the JSON response
48
- print(result)
49
- return result
50
- except requests.exceptions.RequestException as e:
51
- print("Error:", e)
52
- return None
53
-
54
- def generate(
55
- prompt, history=[], temperature=0, max_new_tokens=2000, top_p=0.95, repetition_penalty=1.0,
56
- ):
57
- return call_api(prompt)
58
- temperature = float(temperature)
59
- if temperature < 1e-2:
60
- temperature = 1e-2
61
- top_p = float(top_p)
62
-
63
- generate_kwargs = dict(
64
- temperature=temperature,
65
- max_new_tokens=max_new_tokens,
66
- top_p=top_p,
67
- repetition_penalty=repetition_penalty,
68
- do_sample=True,
69
- seed=42,
70
- )
71
-
72
- #formatted_prompt = format_prompt(prompt, history)
73
-
74
- #stream = client.text_generation(prompt, **generate_kwargs, stream=True, details=False, return_full_text=False)
75
- response = client.text_generation(prompt, **generate_kwargs, stream=False, details=False, return_full_text=False)
76
- print(response)
77
- return response
78
- #output = ""
79
-
80
- #for response in stream:
81
- # yield response.token.text.encode('utf-8')
82
 
83
  app = Flask(__name__)
84
  CORS(app)
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  @app.route('/health', methods=['GET'])
87
  def health():
88
  return jsonify({"status": "ok"})
89
 
90
- @app.route('/completion', methods=['POST'])
91
- def completion_route():
92
- data = request.get_json()
93
- prompt = data.get('prompt', '')
94
- #truncated_prompt = prompt[:32768]
95
- return Response(generate(prompt[:52768]), content_type='text/plain; charset=utf-8', status=200, direct_passthrough=True)
96
-
97
  @app.route('/getsummary', methods=['POST'])
98
- def getsummary_route():
99
  data = request.get_json()
100
  text = data.get('text', '')
101
- # pages = split_text(text)
102
- # result = ''
103
- # for page in pages:
104
- # summary_prompt = f'''''<s>[INST]Исправь в тексте, сделанным роботом при транскрибации аудиозаписи совещания, плохо-читаемые предложения. Проверь каждое предложение на согласованность падежей, окончаний, на наличие и правильное использование предлогов, в реплике каждого спикера исправь пунктуацию, чтобы смысл и был четкий и понятный, также удали дискурсивные слова, не несущие смысла, сделай текст аккуратным и легко читабельным и пиши только на русском языке: {page}[/INST]'
105
- # response = generate(summary_prompt[:52000])
106
- # result = result + '\n'+response
107
 
108
- summary_prompt = f'<s>[INST]Исправь в тексте, сделанным роботом при транскрибации аудиозаписи совещания, плохо-читаемые предложения. Проверь каждое предложение на согласованность падежей, окончаний, на наличие и правильное использование предлогов, в реплике каждого спикера исправь пунктуацию, чтобы смысл и был четкий и понятный, также удали дискурсивные слова, не несущие смысла, сделай текст аккуратным и легко читабельным и пиши только на русском языке: {text}[/INST]'
109
- result = generate(summary_prompt[:52000])
110
- return jsonify({'result': result})
111
 
112
  @app.route('/cleantext', methods=['POST'])
113
- def cleantext_route():
114
  data = request.get_json()
115
  text = data.get('text', '')
116
 
117
- summary_prompt = f'<s>[INST]Ты мой помощник. Ты отвечаешь только на русском языке. Сформируй краткое изложение следующего текста: {text}[/INST]'
118
- response = generate(summary_prompt[:52000])
119
-
120
  return jsonify({'result': response})
121
 
122
  @app.route('/getfollowup', methods=['POST'])
123
- def getfollowup_route():
124
  data = request.get_json()
125
  text = data.get('text', '')
126
 
127
- summary_prompt = f'<s>[INST]Ты мой помощник. Ты отвечаешь только на русском языке. Выпиши из текста нумерованным списком обсуждаемые темы и у каждой темы сделай подпунктами обсуждаемые вопросы. Текст: {text}[/INST]'
128
- response = generate(summary_prompt[:52000])
129
-
130
  return jsonify({'result': response})
131
 
132
  @app.route('/getagenda', methods=['POST'])
133
- def getagenda_route():
134
  data = request.get_json()
135
  text = data.get('text', '')
136
 
137
- summary_prompt = f'<s>[INST]Ты мой помощник. Ты отвечаешь только на русском языке. Выпиши из текста в виде списка какие задачи были поставлены для конкретных исполнителей с указанием сроков, если это возможно определить. Если задача есть в списке, то не добавляй идентичную. Текст: {text}[/INST]'
138
- response = generate(summary_prompt[:52000])
139
-
140
  return jsonify({'result': response})
141
 
142
  @app.route('/gethighlights', methods=['POST'])
143
- def gethighlights_route():
144
  data = request.get_json()
145
  text = data.get('text', '')
146
 
147
- summary_prompt = f'<s>[INST]Ты мой помощник. Ты отвечаешь только на русском языке. Сформируй на основе текста два нумерованных списка: 1. какие проблемы были озвучены в тексте 2. какие предложения были сформулированы. Текст: {text}[/INST]'
148
- response = generate(summary_prompt[:52000])
149
-
150
  return jsonify({'result': response})
151
 
152
 
153
  @app.route('/getprojectinfo', methods=['POST'])
154
- def getprojectinfo_route():
155
  data = request.get_json()
156
  text = data.get('text', '')
157
 
158
  main_prompts = []
159
- prompt1 = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
160
- Найди в тексте ИМЕНА СОБСТВЕННЫЕ участников обсуждения. Кто говорил во вре��я совещания?
161
- Формат ответа: "Участники: *здесь ФИО говорящих из текста*". Если имя говорящего не указано, то отвечай так: "не указано".
162
- Текст совещания: "{text}"[/INST]''')
163
-
164
- prompt2 = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
165
- Представь, что ты лучший в мире смысловик и копирайтер.
166
- Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
167
- Каков ход совещания в этом тексте?
168
- Формат ответа: "Повестка встречи: *здесь текст*".
169
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
170
- Скрипт: {text}[/INST]''')
171
- main_prompts.append(prompt1)
172
- main_prompts.append(prompt2)
173
 
174
  main_info =''
175
  for i in main_prompts:
176
- result = generate(i[:52000])
177
  if result is None:
178
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
179
  main_info += '\n\n'+result+'\n\n'
180
 
181
  final = main_info
182
  final = final.replace("Конец ответа", "")
183
- #final = final.replace('\n', ' ')
184
  final = final.replace('</s>', '')
185
  final = final.strip()
186
  return jsonify({'result': final})
187
 
188
 
189
  @app.route('/getprojectlist', methods=['POST'])
190
- def getprojectlist_route():
191
  data = request.get_json()
192
  text = data.get('text', '')
193
 
194
  main_prompts = []
195
- prompt1 = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
196
- Найди в тексте ИМЕНА СОБСТВЕННЫЕ участников обсуждения. Кто говорил во время совещания?
197
- Формат ответа: "Участники: *здесь ФИО говорящих из текста*". Если имя говорящего не указано, то отвечай так: "не указано".
198
- Текст совещания: "{text}"[/INST]''')
199
-
200
- prompt2 = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
201
- Представь, что ты лучший в мире смысловик и копирайтер.
202
- Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
203
- Каков ход совещания в этом тексте?
204
- Формат ответа: "Повестка встречи: *здесь текст*".
205
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
206
- Скрипт: {text}[/INST]''')
207
- main_prompts.append(prompt1)
208
- main_prompts.append(prompt2)
209
 
210
  main_info =''
211
  for i in main_prompts:
212
- result = generate(i[:52000])
213
  if result is None:
214
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
215
  main_info += '\n\n'+result+'\n\n'
216
 
217
  proj_prompt = []
218
- prompt = (f'''<s>[INST]Вы отвечаете только на РУССКОМ языке.
219
- 1. Найти все упоминания слов "проект" или "проекты" в тексте.
220
- 2. Прочитать контекст вокруг упоминаний слова "проект" или "проекты" и определить, какие проекты упоминаются.
221
- 3. Составить список названий проектов, которые были найдены в тексте.
222
- Используй только такой формат ответа: "Проект №: *суть кратко*". Конец ответа.
223
- Ты больше ничего не говоришь, не комментируешь, не добавляешь.
224
- Текст: {text}[/INST]''')
225
- proj_prompt.append(prompt)
226
 
227
  list_of_projects =''
228
  for i in proj_prompt:
229
- result = generate(i[:52000])
230
  if result is None:
231
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
232
  list_of_projects += result
@@ -235,7 +181,7 @@ def getprojectlist_route():
235
  proj = [delimiter+x for x in list_of_projects.split(delimiter) if x]
236
  proj = proj[1:]
237
 
238
- proj_ = []
239
  for i in proj:
240
  a = i.replace("Проект №", "")
241
  a = a.replace("Конец ответа", "")
@@ -244,21 +190,16 @@ def getprojectlist_route():
244
  a = a.replace('\n', ' ')
245
  a = a.replace('</s>', ' ')
246
  a = a.strip()
247
- proj_.append(a)
248
 
249
  check_prompts = []
250
 
251
- checking = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке. Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
252
- У меня есть список проектов. Такие проекты имеются в тексте и являются основными? Список проектов: {proj_}.
253
- Ты обязательно используешь ТОЛЬКО такой формат ответа:
254
- "Да: Проект № *название проекта*." ИЛИ "Нет, такого проекта нет".
255
- Ты больше ничего не говоришь, не комментируешь, не добавляешь.
256
- Текст: {text}[/INST]''')
257
  check_prompts.append(checking)
258
 
259
  real_projects = ''
260
  for i in check_prompts:
261
- result = generate(i[:52000])
262
  if result is None:
263
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
264
  real_projects += result
@@ -267,7 +208,7 @@ def getprojectlist_route():
267
  return jsonify({'result': real_projects_list})
268
 
269
  @app.route('/getprojectdetails', methods=['POST'])
270
- def getinfobyproject_route():
271
  data = request.get_json()
272
  text = data.get('text', '')
273
  real_projects_list = data.get('projects', {})
@@ -275,43 +216,17 @@ def getinfobyproject_route():
275
  project_prompts = {}
276
  if real_projects_list:
277
  for i in real_projects_list:
278
- if not i or i.strip() == "": # This checks for None, empty string, and strings with only spaces
279
  continue
280
 
281
- prompt_aim = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке. Представь, что ты лучший в мире смысловик и копирайтер.
282
- Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
283
- Опираясь на данный текст, определи цель ЭТОГО проекта: {i}. Выбирай и анализируй информацию только об ЭТОМ проекте.
284
- Формат ответа: "Проект: *название проекта*. Цель этого проекта: *здесь текст*".
285
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
286
- Текст: {text}[/INST]''')
287
- gk = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке. Представь, что ты лучший в мире смысловик и копирайтер.
288
- Сделай глубокий вдох и, думая шаг за шагом, ответь на вопрос: Какие преимущества (выгоду) получит группа компаний по итогам этого проекта: {i}.
289
- Формат ответа: "По факту реализации проекта Группа Компаний Получит: *здесь текст*".
290
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
291
- Скрипт: {text}[/INST]''')
292
- budget = (f'<s>[INST]Ты всегда отвечаешь на РУССКОМ языке. +\
293
- Найди в тексте информацию о бюджете этого проекта: {i} . +\
294
- Формат ответа: "Бюджет проекта: *здесь цифра*". Если бюджет проекта не указан, то отвечай так: "Бюджет: Такой информации нет."+\
295
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке. +\
296
- Скрипт: {text}[/INST]')
297
- ec_ef = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
298
- Поразмышляй об этом тексте. Как ты думаешь, в чем заключается экономический эффект (экономические преимущества) для компании от этого проекта: {i}. Выбирай и анализируй информацию только об ЭТОМ проекте.
299
- Формат ответа: "Экономический эффект от проекта: *здесь ответ на вопрос*".
300
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
301
- Скрипт: {text}[/INST]''')
302
- deadline = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке.
303
- Найди в тексте дату, когда должен быть реализован ЭТОТ проект: {i}. Выбирай и анализируй информацию только об ЭТОМ проекте: {i}.
304
- Формат ответа: "Срок реализации: *здесь текст*".
305
- Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
306
- Скрипт: {text}[/INST]''')
307
- new_plan = (f'<s>[INST]Ты всегда отвечаешь на РУССКОМ языке! Только на русском языке. Выбирай и анализируй информацию на русском языке только об ЭТОМ проекте: {i} .+\
308
- Представь, что ты лучший в мире смысловик и копирайтер. +\
309
- Сделай глубокий вдох и, думая шаг за шагом, ответь на русском языке на вопрос: Какие действия участники решили предпринять, чтобы отлично выполнить проект?+\
310
- Используй такой формат ответа: "Решения: *текст*". Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь! Ищи ответ в тексте: {text}. Отвечай на русском языке!!! [/INST]')
311
- conclusion = (f'''<s>[INST]Ты всегда отвечаешь на РУССКОМ языке. Поразмышляй о тексте.
312
- Какой вывод заключили участники относительно ЭТОГО проекта: {i}.
313
- Используй такой формат ответа: "Вывод: *Тут описывается принятое решение.*." Конец ответа.
314
- Текст: {text}[/INST]''')
315
  p = [prompt_aim, gk, budget, ec_ef, deadline, new_plan, conclusion]
316
  project_prompts[i] = {}
317
  project_prompts[i]['prompts'] = p
@@ -322,7 +237,7 @@ def getinfobyproject_route():
322
  final = {}
323
  for project_name, project in project_prompts.items():
324
  for prompt in project['prompts']:
325
- result = generate(prompt[:52000])
326
  if result is not None:
327
  final[project_name] = final.get(project_name, '') + '\n\n'+result + '\n\n'
328
  final[project_name] = final[project_name].replace("Конец ответа", "")
 
1
  from flask import Flask, request, Response, jsonify
 
2
  from flask_cors import CORS
3
  import json
 
4
  import re
5
+ import os
6
+ from llm.common import LlmParams, LlmPredictParams
7
+ from llm.deepinfra_api import DeepInfraApi
8
+ from llm import prompts
9
+ from dotenv import load_dotenv
10
 
11
+ # Загрузка переменных окружения из файла .env
12
+ load_dotenv()
13
 
14
+ LLM_API_URL = os.getenv("LLM_API_URL", "https://api.deepinfra.com")
15
+ LLM_API_KEY = os.getenv("DEEPINFRA_API_KEY", "")
16
+ LLM_NAME = os.getenv("LLM_NAME", "meta-llama/Llama-3.3-70B-Instruct-Turbo")
17
+
18
+ default_llm_params = LlmParams(url=LLM_API_URL,api_key=LLM_API_KEY, model=LLM_NAME, predict_params=LlmPredictParams(temperature=0.15, top_p=0.95, min_p=0.05, seed=42, repetition_penalty=1.2, presence_penalty=1.1, max_tokens=6000))
19
+ llm_api = DeepInfraApi(default_llm_params)
20
 
21
  def format_prompt(message, history):
22
  prompt = "<s>"
 
45
  result.append(current_chunk.strip())
46
  return result
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  app = Flask(__name__)
50
  CORS(app)
51
 
52
+ @app.route('/extracttable', methods=['POST'])
53
+ async def extracttable_route():
54
+ data = request.get_json()
55
+ text = data.get('text', '')
56
+
57
+ prompt = prompts.LLM_PROMPT_EXTRACT_TABLE.format(query = text)
58
+ response = await llm_api.predict(prompt[:150000])
59
+
60
+ result = {"response": None, "error": None, "raw": response} # По умолчанию сохраняем всю строку
61
+
62
+ if "JSON: " not in response:
63
+ result["error"] = "Строка не содержит 'JSON: '"
64
+ return result
65
+
66
+ prefix, json_str = response.split("JSON: ", 1)
67
+ json_str = json_str.strip()
68
+
69
+ if not json_str:
70
+ result["error"] = "После 'JSON: ' отсутствует JSON"
71
+ return result
72
+
73
+ try:
74
+ result["response"] = json.loads(json_str)
75
+ result["raw"] = prefix.strip() # Остаток перед "JSON: "
76
+ except json.JSONDecodeError as e:
77
+ result["error"] = f"Ошибка декодирования JSON: {e}"
78
+
79
+ return jsonify(result)
80
+
81
  @app.route('/health', methods=['GET'])
82
  def health():
83
  return jsonify({"status": "ok"})
84
 
 
 
 
 
 
 
 
85
  @app.route('/getsummary', methods=['POST'])
86
+ async def getsummary_route():
87
  data = request.get_json()
88
  text = data.get('text', '')
 
 
 
 
 
 
89
 
90
+ prompt = prompts.GET_SUMMARY.format(text=text)
91
+ response = await llm_api.predict(prompt[:150000])
92
+ return jsonify({'result': response})
93
 
94
  @app.route('/cleantext', methods=['POST'])
95
+ async def cleantext_route():
96
  data = request.get_json()
97
  text = data.get('text', '')
98
 
99
+ prompt = prompts.CLEAN_TEXT.format(text=text)
100
+ response = await llm_api.predict(prompt[:150000])
 
101
  return jsonify({'result': response})
102
 
103
  @app.route('/getfollowup', methods=['POST'])
104
+ async def getfollowup_route():
105
  data = request.get_json()
106
  text = data.get('text', '')
107
 
108
+ prompt = prompts.GET_FOLLOWUP.format(text=text)
109
+ response = await llm_api.predict(prompt[:150000])
 
110
  return jsonify({'result': response})
111
 
112
  @app.route('/getagenda', methods=['POST'])
113
+ async def getagenda_route():
114
  data = request.get_json()
115
  text = data.get('text', '')
116
 
117
+ prompt = prompts.GET_AGENDA.format(text=text)
118
+ response = await llm_api.predict(prompt[:150000])
 
119
  return jsonify({'result': response})
120
 
121
  @app.route('/gethighlights', methods=['POST'])
122
+ async def gethighlights_route():
123
  data = request.get_json()
124
  text = data.get('text', '')
125
 
126
+ prompt = prompts.GET_HIGHLIGHTS.format(text=text)
127
+ response = await llm_api.predict(prompt[:150000])
 
128
  return jsonify({'result': response})
129
 
130
 
131
  @app.route('/getprojectinfo', methods=['POST'])
132
+ async def getprojectinfo_route():
133
  data = request.get_json()
134
  text = data.get('text', '')
135
 
136
  main_prompts = []
137
+ main_prompts.append(prompts.GET_PROJECT_INFO_NAMES.format(text=text))
138
+ main_prompts.append(prompts.GET_PROJECT_INFO_AGENDA.format(text=text))
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  main_info =''
141
  for i in main_prompts:
142
+ result = await llm_api.predict(i[:150000])
143
  if result is None:
144
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
145
  main_info += '\n\n'+result+'\n\n'
146
 
147
  final = main_info
148
  final = final.replace("Конец ответа", "")
 
149
  final = final.replace('</s>', '')
150
  final = final.strip()
151
  return jsonify({'result': final})
152
 
153
 
154
  @app.route('/getprojectlist', methods=['POST'])
155
+ async def getprojectlist_route():
156
  data = request.get_json()
157
  text = data.get('text', '')
158
 
159
  main_prompts = []
160
+ main_prompts.append(prompts.GET_PROJECT_INFO_NAMES.format(text=text))
161
+ main_prompts.append(prompts.GET_PROJECT_INFO_AGENDA.format(text=text))
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  main_info =''
164
  for i in main_prompts:
165
+ result = await llm_api.predict(i[:150000])
166
  if result is None:
167
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
168
  main_info += '\n\n'+result+'\n\n'
169
 
170
  proj_prompt = []
171
+ proj_prompt.append(prompts.GET_PROJECT_LIST.format(text=text))
 
 
 
 
 
 
 
172
 
173
  list_of_projects =''
174
  for i in proj_prompt:
175
+ result = await llm_api.predict(i[:150000])
176
  if result is None:
177
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
178
  list_of_projects += result
 
181
  proj = [delimiter+x for x in list_of_projects.split(delimiter) if x]
182
  proj = proj[1:]
183
 
184
+ projects = []
185
  for i in proj:
186
  a = i.replace("Проект №", "")
187
  a = a.replace("Конец ответа", "")
 
190
  a = a.replace('\n', ' ')
191
  a = a.replace('</s>', ' ')
192
  a = a.strip()
193
+ projects.append(a)
194
 
195
  check_prompts = []
196
 
197
+ checking = prompts.GET_PROJECT_LIST_CHECK_PROJECT.format(text=text, projects=projects)
 
 
 
 
 
198
  check_prompts.append(checking)
199
 
200
  real_projects = ''
201
  for i in check_prompts:
202
+ result = await llm_api.predict(i[:150000])
203
  if result is None:
204
  return jsonify({'error': 'Сервер LLM временно недоступен. Попробуйте повторить запрос через несколько минут.'})
205
  real_projects += result
 
208
  return jsonify({'result': real_projects_list})
209
 
210
  @app.route('/getprojectdetails', methods=['POST'])
211
+ async def getinfobyproject_route():
212
  data = request.get_json()
213
  text = data.get('text', '')
214
  real_projects_list = data.get('projects', {})
 
216
  project_prompts = {}
217
  if real_projects_list:
218
  for i in real_projects_list:
219
+ if not i or i.strip() == "":
220
  continue
221
 
222
+ prompt_aim = prompts.GET_PROJECT_DETAILS_AIM.format(text=text, project=i)
223
+ gk = prompts.GET_PROJECT_DETAILS_VALUE.format(text=text, project=i)
224
+ budget = prompts.GET_PROJECT_DETAILS_BUDGET.format(text=text, project=i)
225
+ ec_ef = prompts.GET_PROJECT_DETAILS_ECO_EFFECT.format(text=text, project=i)
226
+ deadline = prompts.GET_PROJECT_DETAILS_DEADLINE.format(text=text, project=i)
227
+ new_plan = prompts.GET_PROJECT_DETAILS_NEW_PLAN.format(text=text, project=i)
228
+ conclusion = prompts.GET_PROJECT_DETAILS_CONCLUSION.format(text=text, project=i)
229
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  p = [prompt_aim, gk, budget, ec_ef, deadline, new_plan, conclusion]
231
  project_prompts[i] = {}
232
  project_prompts[i]['prompts'] = p
 
237
  final = {}
238
  for project_name, project in project_prompts.items():
239
  for prompt in project['prompts']:
240
+ result = await llm_api.predict(prompt[:150000])
241
  if result is not None:
242
  final[project_name] = final.get(project_name, '') + '\n\n'+result + '\n\n'
243
  final[project_name] = final[project_name].replace("Конец ответа", "")
llm/common.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional, List, Protocol
3
+ from abc import ABC, abstractmethod
4
+
5
+ class LlmPredictParams(BaseModel):
6
+ """
7
+ Параметры для предсказания LLM.
8
+ """
9
+ system_prompt: Optional[str] = Field(None, description="Системный промпт.")
10
+ user_prompt: Optional[str] = Field(None, description="Шаблон промпта для передачи от роли user.")
11
+ n_predict: Optional[int] = None
12
+ temperature: Optional[float] = None
13
+ top_k: Optional[int] = None
14
+ top_p: Optional[float] = None
15
+ min_p: Optional[float] = None
16
+ seed: Optional[int] = None
17
+ repeat_penalty: Optional[float] = None
18
+ repeat_last_n: Optional[int] = None
19
+ retry_if_text_not_present: Optional[str] = None
20
+ retry_count: Optional[int] = None
21
+ presence_penalty: Optional[float] = None
22
+ frequency_penalty: Optional[float] = None
23
+ n_keep: Optional[int] = None
24
+ cache_prompt: Optional[bool] = None
25
+ stop: Optional[List[str]] = None
26
+
27
+
28
+ class LlmParams(BaseModel):
29
+ """
30
+ Основные параметры для LLM.
31
+ """
32
+ url: str
33
+ model: Optional[str] = Field(None, description="Предполагается, что для локального API этот параметр не будет указываться, т.к. будем брать первую модель из списка потому, что модель доступна всего одна. Для deepinfra такой подход не подойдет и модель нужно задавать явно.")
34
+ type: Optional[str] = None
35
+ default: Optional[bool] = None
36
+ template: Optional[str] = None
37
+ predict_params: Optional[LlmPredictParams] = None
38
+ api_key: Optional[str] = None
39
+
40
+ class LlmApiProtocol(Protocol):
41
+ async def tokenize(self, prompt: str) -> Optional[dict]:
42
+ ...
43
+ async def detokenize(self, tokens: List[int]) -> Optional[str]:
44
+ ...
45
+ async def trim_sources(self, sources: str, user_request: str, system_prompt: str = None) -> dict:
46
+ ...
47
+ async def predict(self, prompt: str) -> str:
48
+ ...
49
+
50
+ class LlmApi:
51
+ """
52
+ Базовый клас для работы с API LLM.
53
+ """
54
+ params: LlmParams = None
55
+
56
+ def __init__(self):
57
+ self.params = None
58
+
59
+ def set_params(self, params: LlmParams):
60
+ self.params = params
61
+
62
+ def create_headers(self) -> dict[str, str]:
63
+ headers = {"Content-Type": "application/json"}
64
+
65
+ if self.params.api_key is not None:
66
+ headers["Authorization"] = self.params.api_key
67
+
68
+ return headers
69
+
llm/deepinfra_api.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import Optional, List
3
+ import httpx
4
+ from llm.common import LlmParams, LlmApi
5
+
6
+ class DeepInfraApi(LlmApi):
7
+ """
8
+ Класс для работы с API vllm.
9
+ """
10
+
11
+ def __init__(self, params: LlmParams):
12
+ super().__init__()
13
+ super().set_params(params)
14
+
15
+ async def get_models(self) -> List[str]:
16
+ """
17
+ Выполняет GET-запрос к API для получения списка доступных моделей.
18
+
19
+ Возвращает:
20
+ list[str]: Список идентификаторов моделей.
21
+ Если произошла ошибка или данные недоступны, возвращается пустой список.
22
+
23
+ Исключения:
24
+ Все ошибки HTTP-запросов логируются в консоль, но не выбрасываются дальше.
25
+ """
26
+ try:
27
+ async with httpx.AsyncClient() as client:
28
+ response = await client.get(f"{self.params.url}/v1/openai/models", headers=super().create_headers())
29
+ if response.status_code == 200:
30
+ json_data = response.json()
31
+ return [item['id'] for item in json_data.get('data', [])]
32
+ except httpx.RequestError as error:
33
+ print('Error fetching models:', error)
34
+ return []
35
+
36
+ def create_messages(self, prompt: str) -> List[dict]:
37
+ """
38
+ Создает сообщения для LLM на основе переданного промпта и системного промпта (если он задан).
39
+
40
+ Args:
41
+ prompt (str): Пользовательский промпт.
42
+
43
+ Returns:
44
+ list[dict]: Список сообщений с ролями и содержимым.
45
+ """
46
+ actual_prompt = self.apply_llm_template_to_prompt(prompt)
47
+ messages = []
48
+ if self.params.predict_params and self.params.predict_params.system_prompt:
49
+ messages.append({"role": "system", "content": self.params.predict_params.system_prompt})
50
+ messages.append({"role": "user", "content": actual_prompt})
51
+ return messages
52
+
53
+ def apply_llm_template_to_prompt(self, prompt: str) -> str:
54
+ """
55
+ Применяет шаблон LLM к переданному промпту, если он задан.
56
+
57
+ Args:
58
+ prompt (str): Пользовательский промпт.
59
+
60
+ Returns:
61
+ str: Промпт с примененным шаблоном (или оригинальный, если шаблон отсутствует).
62
+ """
63
+ actual_prompt = prompt
64
+ if self.params.template is not None:
65
+ actual_prompt = self.params.template.replace("{{PROMPT}}", actual_prompt)
66
+ return actual_prompt
67
+
68
+ async def tokenize(self, prompt: str) -> Optional[dict]:
69
+ raise NotImplementedError("This function is not supported.")
70
+
71
+ async def detokenize(self, tokens: List[int]) -> Optional[str]:
72
+ raise NotImplementedError("This function is not supported.")
73
+
74
+ async def create_request(self, prompt: str) -> dict:
75
+ """
76
+ Создает запрос для предсказания на основе параметров LLM.
77
+
78
+ Args:
79
+ prompt (str): Промпт для запроса.
80
+
81
+ Returns:
82
+ dict: Словарь с параметрами для выполнения запроса.
83
+ """
84
+
85
+ request = {
86
+ "stream": False,
87
+ "model": self.params.model,
88
+ }
89
+
90
+ predict_params = self.params.predict_params
91
+ if predict_params:
92
+ if predict_params.stop:
93
+ non_empty_stop = list(filter(lambda o: o != "", predict_params.stop))
94
+ if non_empty_stop:
95
+ request["stop"] = non_empty_stop
96
+
97
+ if predict_params.n_predict is not None:
98
+ request["max_tokens"] = int(predict_params.n_predict or 0)
99
+
100
+ request["temperature"] = float(predict_params.temperature or 0)
101
+ if predict_params.top_k is not None:
102
+ request["top_k"] = int(predict_params.top_k)
103
+
104
+ if predict_params.top_p is not None:
105
+ request["top_p"] = float(predict_params.top_p)
106
+
107
+ if predict_params.min_p is not None:
108
+ request["min_p"] = float(predict_params.min_p)
109
+
110
+ if predict_params.seed is not None:
111
+ request["seed"] = int(predict_params.seed)
112
+
113
+ if predict_params.n_keep is not None:
114
+ request["n_keep"] = int(predict_params.n_keep)
115
+
116
+ if predict_params.cache_prompt is not None:
117
+ request["cache_prompt"] = bool(predict_params.cache_prompt)
118
+
119
+ if predict_params.repeat_penalty is not None:
120
+ request["repetition_penalty"] = float(predict_params.repeat_penalty)
121
+
122
+ if predict_params.repeat_last_n is not None:
123
+ request["repeat_last_n"] = int(predict_params.repeat_last_n)
124
+
125
+ if predict_params.presence_penalty is not None:
126
+ request["presence_penalty"] = float(predict_params.presence_penalty)
127
+
128
+ if predict_params.frequency_penalty is not None:
129
+ request["frequency_penalty"] = float(predict_params.frequency_penalty)
130
+
131
+ request["messages"] = self.create_messages(prompt)
132
+ return request
133
+
134
+ async def trim_sources(self, sources: str, user_request: str, system_prompt: str = None) -> dict:
135
+ raise NotImplementedError("This function is not supported.")
136
+
137
+ async def predict(self, prompt: str) -> str:
138
+ """
139
+ Выполняет запрос к API и возвращает результат.
140
+
141
+ Args:
142
+ prompt (str): Входной текст для предсказания.
143
+
144
+ Returns:
145
+ str: Сгенерированный текст.
146
+ """
147
+ async with httpx.AsyncClient() as client:
148
+ request = await self.create_request(prompt)
149
+ response = await client.post(f"{self.params.url}/v1/openai/chat/completions", headers=super().create_headers(), json=request)
150
+ if response.status_code == 200:
151
+ return response.json()["choices"][0]["message"]["content"]
llm/prompts.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ LLM_PROMPT_EXTRACT_TABLE = """
2
+ Обработай текст, верни json после слова "JSON:":
3
+ {query}"""
4
+
5
+ GET_SUMMARY = 'Исправь в тексте, сделанным роботом при транскрибации аудиозаписи совещания, плохо-читаемые предложения. Проверь каждое предложение на согласованность падежей, окончаний, на наличие и правильное использование предлогов, в реплике каждого спикера исправь пунктуацию, чтобы смысл и был четкий и понятный, также удали дискурсивные слова, не несущие смысла, сделай текст аккуратным и легко читабельным и пиши только на русском языке: {text}'
6
+
7
+ CLEAN_TEXT = """
8
+ Ты мой помощник. Ты отвечаешь только на русском языке. Сформируй краткое изложение следующего текста: {text}
9
+ """
10
+
11
+ GET_FOLLOWUP = """
12
+ Ты мой помощник. Ты отвечаешь только на русском языке. Выпиши из текста нумерованным списком обсуждаемые темы и у каждой темы сделай подпунктами обсуждаемые вопросы. Текст: {text}
13
+ """
14
+
15
+ GET_AGENDA = """
16
+ Ты мой помощник. Ты отвечаешь только на русском языке. Выпиши из текста в виде списка какие задачи были поставлены для конкретных исполнителей с указанием сроков, если это возможно определить. Если задача есть в списке, то не добавляй идентичную. Текст: {text}
17
+ """
18
+
19
+ GET_HIGHLIGHTS = """
20
+ Ты мой помощник. Ты отвечаешь только на русском языке. Сформируй на основе текста два нумерованных списка: 1. какие проблемы были озвучены в тексте 2. какие предложения были сформулированы. Текст: {text}
21
+ """
22
+
23
+ GET_PROJECT_INFO_NAMES = """
24
+ Ты всегда отвечаешь на РУССКОМ языке.
25
+ Найди в тексте ИМЕНА СОБСТВЕННЫЕ участников обсуждения. Кто говорил во время совещания?
26
+ Формат ответа: "Участники: *здесь ФИО говорящих из текста*". Если имя говорящего не указано, то отвечай так: "не указано".
27
+ Текст совещания: "{text}"
28
+ """
29
+
30
+ GET_PROJECT_INFO_AGENDA = """
31
+ Ты всегда отвечаешь на РУССКОМ языке.
32
+ Представь, что ты лучший в мире смысловик и копирайтер.
33
+ Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
34
+ Каков ход совещания в этом тексте?
35
+ Формат ответа: "Повестка встречи: *здесь текст*".
36
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
37
+ Скрипт: {text}
38
+ """
39
+
40
+ GET_PROJECT_LIST = """
41
+ Вы отвечаете только на РУССКОМ языке.
42
+ 1. Найти все упоминания слов "проект" или "проекты" в тексте.
43
+ 2. Прочитать контекст вокруг упоминаний слова "проект" или "проекты" и определить, какие проекты упоминаются.
44
+ 3. Составить список названий проектов, которые были найдены в тексте.
45
+ Используй только такой формат ответа: "Проект №: *суть кратко*". Конец ответа.
46
+ Ты больше ничего не говоришь, не комментируешь, не добавляешь.
47
+ Текст: {text}
48
+ """
49
+
50
+ GET_PROJECT_LIST_CHECK_PROJECT = """
51
+ Ты всегда отвечаешь на РУССКОМ языке. Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
52
+ У меня есть список проектов. Такие проекты имеются в тексте и являются основными? Список проектов: {projects}.
53
+ Ты обязательно используешь ТОЛЬКО такой формат ответа:
54
+ "Да: Проект № *название проекта*." ИЛИ "Нет, такого проекта нет".
55
+ Ты больше ничего не говоришь, не комментируешь, не добавляешь.
56
+ Текст: {text}
57
+ """
58
+
59
+ GET_PROJECT_DETAILS_AIM = """Ты всегда отвечаешь на РУССКОМ языке. Представь, что ты лучший в мире смысловик и копирайтер.
60
+ Сделай глубокий вдох и, думая шаг за шагом, выполни задание:
61
+ Опираясь на данный текст, определи цель ЭТОГО проекта: {project}. Выбирай и анализируй информацию только об ЭТОМ проекте.
62
+ Формат ответа: "Проект: *название проекта*. Цель этого проекта: *здесь текст*".
63
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
64
+ Текст: {text}"""
65
+
66
+ GET_PROJECT_DETAILS_VALUE = """Ты всегда отвечаешь на РУССКОМ языке. Представь, что ты лучший в мире смысловик и копирайтер.
67
+ Сделай глубокий вдох и, думая шаг за шагом, ответь на вопрос: Какие преимущества (выгоду) получит группа компаний по итогам этого проекта: {project}.
68
+ Формат ответа: "По факту реализации проекта Группа Компаний Получит: *здесь текст*".
69
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
70
+ Скрипт: {text}"""
71
+
72
+ GET_PROJECT_DETAILS_BUDGET = """Ты всегда отвечаешь на РУССКОМ языке.
73
+ Найди в тексте информацию о бюджете этого проекта: {project} .
74
+ Формат ответа: "Бюджет проекта: *здесь цифра*". Если бюджет проекта не указан, то отвечай так: "Бюджет: Такой информации нет."
75
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
76
+ Скрипт: {text}"""
77
+
78
+ GET_PROJECT_DETAILS_ECO_EFFECT = """Ты всегда отвечаешь на РУССКОМ языке.
79
+ Поразмышляй об этом тексте. Как ты думаешь, в чем заключается экономический эффект (экономические преимущества) для компании от этого проекта: {project}. Выбирай и анализируй информацию только об ЭТОМ проекте.
80
+ Формат ответа: "Экономический эффект от проекта: *здесь ответ на вопрос*".
81
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
82
+ Скрипт: {text}"""
83
+
84
+ GET_PROJECT_DETAILS_DEADLINE = """Ты всегда отвечаешь на РУССКОМ языке.
85
+ Найди в тексте дату, когда должен быть реализован ЭТОТ проект: {project}. Выбирай и анализируй информацию только об ЭТОМ проекте: {project}.
86
+ Формат ответа: "Срок реализации: *здесь текст*".
87
+ Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь. Отвечай на русском языке.
88
+ Скрипт: {text}"""
89
+
90
+ GET_PROJECT_DETAILS_NEW_PLAN = """Ты всегда отвечаешь на РУССКОМ языке! Только на русском языке. Выбирай и анализируй информацию на русском языке только об ЭТОМ проекте: {project} .
91
+ Представь, что ты лучший в мире смысловик и копирайтер.
92
+ Сделай глубокий вдох и, думая шаг за шагом, ответь на русском языке на вопрос: Какие действия участники решили предпринять, чтобы отлично выполнить проект?
93
+ Используй такой формат ответа: "Решения: *текст*". Ты больше ничего не говоришь, не комментируешь, не выражаешь свои мысли, вообще ничего больше не говоришь! Ищи ответ в тексте: {text}. Отвечай на русском языке!!!"""
94
+
95
+ GET_PROJECT_DETAILS_CONCLUSION = """Ты всегда отвечаешь на РУССКОМ языке. Поразмышляй о тексте.
96
+ Какой вывод заключили участники относительно ЭТОГО проекта: {project}.
97
+ Используй такой формат ответа: "Вывод: *Тут описывается принятое решение.*." Конец ответа.
98
+ Текст: {text}"""
llm/vllm_api-sync.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import requests
4
+ from typing import Optional, List, Any
5
+ from pydantic import BaseModel, Field
6
+
7
+ class LlmPredictParams(BaseModel):
8
+ """
9
+ Параметры для предсказания LLM.
10
+ """
11
+ system_prompt: Optional[str] = Field(None, description="Системный промпт.")
12
+ user_prompt: Optional[str] = Field(None, description="Шаблон промпта для передачи от роли user.")
13
+ n_predict: Optional[int] = None
14
+ temperature: Optional[float] = None
15
+ top_k: Optional[int] = None
16
+ top_p: Optional[float] = None
17
+ min_p: Optional[float] = None
18
+ seed: Optional[int] = None
19
+ repeat_penalty: Optional[float] = None
20
+ repeat_last_n: Optional[int] = None
21
+ retry_if_text_not_present: Optional[str] = None
22
+ retry_count: Optional[int] = None
23
+ presence_penalty: Optional[float] = None
24
+ frequency_penalty: Optional[float] = None
25
+ n_keep: Optional[int] = None
26
+ cache_prompt: Optional[bool] = None
27
+ stop: Optional[List[str]] = None
28
+
29
+
30
+ class LlmParams(BaseModel):
31
+ """
32
+ Основные параметры для LLM.
33
+ """
34
+ url: str
35
+ type: Optional[str] = None
36
+ default: Optional[bool] = None
37
+ template: Optional[str] = None
38
+ predict_params: Optional[LlmPredictParams] = None
39
+
40
+ class LlmApi:
41
+ """
42
+ Класс для работы с API vllm.
43
+ """
44
+
45
+ params: LlmParams = None
46
+
47
+ def __init__(self, params: LlmParams):
48
+ self.params = params
49
+
50
+
51
+ def get_models(self) -> list[str]:
52
+ """
53
+ Выполняет GET-запрос к API для получения списка доступных моделей.
54
+
55
+ Возвращает:
56
+ list[str]: Список идентификаторов моделей.
57
+ Если произошла ошибка или данные недоступны, возвращается пустой список.
58
+
59
+ Исключения:
60
+ Все ошибки HTTP-запросов логируются в консоль, но не выбрасываются дальше.
61
+ """
62
+
63
+ try:
64
+ response = requests.get(f"{self.params.url}/v1/models", headers={"Content-Type": "application/json"})
65
+
66
+ if response.status_code == 200:
67
+ json_data = response.json()
68
+ result = [item['id'] for item in json_data.get('data', [])]
69
+ return result
70
+
71
+ except requests.RequestException as error:
72
+ print('OpenAiService.getModels error:')
73
+ print(error)
74
+
75
+ return []
76
+
77
+ def create_messages(self, prompt: str) -> list[dict]:
78
+ """
79
+ Создает сообщения для LLM на основе переданного промпта и системного промпта (если он задан).
80
+
81
+ Args:
82
+ prompt (str): Пользовательский промпт.
83
+
84
+ Returns:
85
+ list[dict]: Список сообщений с ролями и содержимым.
86
+ """
87
+ actual_prompt = self.apply_llm_template_to_prompt(prompt)
88
+ messages = []
89
+
90
+ if self.params.predict_params and self.params.predict_params.system_prompt:
91
+ messages.append({"role": "system", "content": self.params.predict_params.system_prompt})
92
+
93
+ messages.append({"role": "user", "content": actual_prompt})
94
+ return messages
95
+
96
+ def apply_llm_template_to_prompt(self, prompt: str) -> str:
97
+ """
98
+ Применяет шаблон LLM к переданному промпту, если он задан.
99
+
100
+ Args:
101
+ prompt (str): Пользовательский промпт.
102
+
103
+ Returns:
104
+ str: Промпт с примененным шаблоном (или оригинальный, если шаблон отсутствует).
105
+ """
106
+ actual_prompt = prompt
107
+ if self.params.template is not None:
108
+ actual_prompt = self.params.template.replace("{{PROMPT}}", actual_prompt)
109
+ return actual_prompt
110
+
111
+ def tokenize(self, prompt: str) -> Optional[dict]:
112
+ """
113
+ Выполняет токенизацию переданного промпта.
114
+
115
+ Args:
116
+ prompt (str): Промпт для токенизации.
117
+
118
+ Returns:
119
+ Optional[dict]: Словарь с токенами и максимальной длиной модели, если запрос успешен.
120
+ Если запрос неуспешен, возвращает None.
121
+ """
122
+ model = self.get_models()[0] if self.get_models() else None
123
+ if not model:
124
+ print("No models available for tokenization.")
125
+ return None
126
+
127
+ actual_prompt = self.apply_llm_template_to_prompt(prompt)
128
+ request_data = {
129
+ "model": model,
130
+ "prompt": actual_prompt,
131
+ "add_special_tokens": False,
132
+ }
133
+
134
+ try:
135
+ response = requests.post(
136
+ f"{self.params.url}/tokenize",
137
+ json=request_data,
138
+ headers={"Content-Type": "application/json"},
139
+ )
140
+
141
+ if response.ok:
142
+ data = response.json()
143
+ if "tokens" in data:
144
+ return {"tokens": data["tokens"], "maxLength": data.get("max_model_len")}
145
+ elif response.status_code == 404:
146
+ print("Tokenization endpoint not found (404).")
147
+ else:
148
+ print(f"Failed to tokenize: {response.status_code}")
149
+ except requests.RequestException as e:
150
+ print(f"Request failed: {e}")
151
+
152
+ return None
153
+
154
+ def detokenize(self, tokens: List[int]) -> Optional[str]:
155
+ """
156
+ Выполняет детокенизацию переданных токенов.
157
+
158
+ Args:
159
+ tokens (List[int]): Список токенов для детокенизации.
160
+
161
+ Returns:
162
+ Optional[str]: Строка, полученная в результате детокенизации, если запрос успешен.
163
+ Если запрос неуспешен, возвращает None.
164
+ """
165
+ model = self.get_models()[0] if self.get_models() else None
166
+ if not model:
167
+ print("No models available for detokenization.")
168
+ return None
169
+
170
+ request_data = {"model": model, "tokens": tokens or []}
171
+
172
+ try:
173
+ response = requests.post(
174
+ f"{self.params.url}/detokenize",
175
+ json=request_data,
176
+ headers={"Content-Type": "application/json"},
177
+ )
178
+
179
+ if response.ok:
180
+ data = response.json()
181
+ if "prompt" in data:
182
+ return data["prompt"].strip()
183
+ elif response.status_code == 404:
184
+ print("Detokenization endpoint not found (404).")
185
+ else:
186
+ print(f"Failed to detokenize: {response.status_code}")
187
+ except requests.RequestException as e:
188
+ print(f"Request failed: {e}")
189
+
190
+ return None
191
+
192
+ def create_request(self, prompt: str) -> dict:
193
+ """
194
+ Создает запрос для предсказания на основе параметров LLM.
195
+
196
+ Args:
197
+ prompt (str): Промпт для запроса.
198
+
199
+ Returns:
200
+ dict: Словарь с параметрами для выполнения запроса.
201
+ """
202
+ llm_params = self.params
203
+ models = self.get_models()
204
+ if not models:
205
+ raise ValueError("No models available to create a request.")
206
+ model = models[0]
207
+
208
+ request = {
209
+ "stream": True,
210
+ "model": model,
211
+ }
212
+
213
+ predict_params = llm_params.predict_params
214
+
215
+ if predict_params:
216
+ if predict_params.stop:
217
+ # Фильтруем пустые строки в stop
218
+ non_empty_stop = list(filter(lambda o: o != "", predict_params.stop))
219
+ if non_empty_stop:
220
+ request["stop"] = non_empty_stop
221
+
222
+ if predict_params.n_predict is not None:
223
+ request["max_tokens"] = int(predict_params.n_predict or 0)
224
+
225
+ request["temperature"] = float(predict_params.temperature or 0)
226
+
227
+ if predict_params.top_k is not None:
228
+ request["top_k"] = int(predict_params.top_k)
229
+
230
+ if predict_params.top_p is not None:
231
+ request["top_p"] = float(predict_params.top_p)
232
+
233
+ if predict_params.min_p is not None:
234
+ request["min_p"] = float(predict_params.min_p)
235
+
236
+ if predict_params.seed is not None:
237
+ request["seed"] = int(predict_params.seed)
238
+
239
+ if predict_params.n_keep is not None:
240
+ request["n_keep"] = int(predict_params.n_keep)
241
+
242
+ if predict_params.cache_prompt is not None:
243
+ request["cache_prompt"] = bool(predict_params.cache_prompt)
244
+
245
+ if predict_params.repeat_penalty is not None:
246
+ request["repetition_penalty"] = float(predict_params.repeat_penalty)
247
+
248
+ if predict_params.repeat_last_n is not None:
249
+ request["repeat_last_n"] = int(predict_params.repeat_last_n)
250
+
251
+ if predict_params.presence_penalty is not None:
252
+ request["presence_penalty"] = float(predict_params.presence_penalty)
253
+
254
+ if predict_params.frequency_penalty is not None:
255
+ request["frequency_penalty"] = float(predict_params.frequency_penalty)
256
+
257
+ # Генерируем сообщения
258
+ request["messages"] = self.create_messages(prompt)
259
+
260
+ return request
261
+
262
+
263
+ def trim_sources(self, sources: str, user_request: str, system_prompt: str = None) -> dict:
264
+ """
265
+ Обрезает текст источников, чтобы уложиться в допустимое количество токенов.
266
+
267
+ Args:
268
+ sources (str): Текст источников.
269
+ user_request (str): Запрос пользователя с примененным шаблоном без текста источников.
270
+ system_prompt (str): Системный промпт, если нужен.
271
+
272
+ Returns:
273
+ dict: Словарь с результатом, количеством токенов до и после обрезки.
274
+ """
275
+ # Токенизация текста источников
276
+ sources_tokens_data = self.tokenize(sources)
277
+ if sources_tokens_data is None:
278
+ raise ValueError("Failed to tokenize sources.")
279
+ max_token_count = sources_tokens_data.get("maxLength", 0)
280
+
281
+ # Токены системного промпта
282
+ system_prompt_token_count = 0
283
+
284
+ if system_prompt is not None:
285
+ system_prompt_tokens = self.tokenize(system_prompt)
286
+ system_prompt_token_count = len(system_prompt_tokens["tokens"]) if system_prompt_tokens else 0
287
+
288
+ # Оригинальное количество токенов
289
+ original_token_count = len(sources_tokens_data["tokens"])
290
+
291
+ # Токенизация пользовательского промпта
292
+ aux_prompt = self.apply_llm_template_to_prompt(user_request)
293
+ aux_tokens_data = self.tokenize(aux_prompt)
294
+
295
+ aux_token_count = len(aux_tokens_data["tokens"]) if aux_tokens_data else 0
296
+
297
+ # Максимально допустимое количество токенов для источников
298
+ max_length = (
299
+ max_token_count
300
+ - (self.params.predict_params.n_predict or 0)
301
+ - aux_token_count
302
+ - system_prompt_token_count
303
+ )
304
+ max_length = max(max_length, 0)
305
+
306
+ # Обрезка токенов источников
307
+ if "tokens" in sources_tokens_data:
308
+ sources_tokens_data["tokens"] = sources_tokens_data["tokens"][:max_length]
309
+ detokenized_prompt = self.detokenize(sources_tokens_data["tokens"])
310
+ if detokenized_prompt is not None:
311
+ sources = detokenized_prompt
312
+ else:
313
+ sources = sources[:max_length]
314
+ else:
315
+ sources = sources[:max_length]
316
+
317
+ # Возврат результата
318
+ return {
319
+ "result": sources,
320
+ "originalTokenCount": original_token_count,
321
+ "slicedTokenCount": len(sources_tokens_data["tokens"]),
322
+ }
323
+
324
+ def predict(self, prompt: str) -> str:
325
+ """
326
+ Выполняет SSE-запрос к API и возвращает собранный результат как текст.
327
+
328
+ Args:
329
+ prompt (str): Входной текст для предсказания.
330
+
331
+ Returns:
332
+ str: Сгенерированный текст.
333
+
334
+ Raises:
335
+ Exception: Если запрос завершился ошибкой.
336
+ """
337
+
338
+ # Создание запроса
339
+ request = self.create_request(prompt)
340
+
341
+ print(f"Predict request. Url: {self.params.url}")
342
+
343
+ response = requests.post(
344
+ f"{self.params.url}/v1/chat/completions",
345
+ headers={"Content-Type": "application/json"},
346
+ json=request,
347
+ stream=True # Для обработки SSE
348
+ )
349
+
350
+ if not response.ok:
351
+ raise Exception(f"Failed to generate text: {response.text}")
352
+
353
+ # Обработка SSE-ответа
354
+ generated_text = ""
355
+ for line in response.iter_lines(decode_unicode=True):
356
+ if line.startswith("data: "):
357
+ try:
358
+ data = json.loads(line[len("data: "):].strip())
359
+
360
+ # Проверка завершения генерации
361
+ if data == "[DONE]":
362
+ break
363
+
364
+ # Получение текста из ответа
365
+ if "choices" in data and data["choices"]:
366
+ token_value = data["choices"][0].get("delta", {}).get("content", "")
367
+ generated_text += token_value.replace("</s>", "")
368
+
369
+ except json.JSONDecodeError:
370
+ continue # Игнорирование строк, которые не удалось декодировать
371
+
372
+ return generated_text
373
+
374
+
375
+
llm/vllm_api.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import Optional, List
3
+
4
+ import httpx
5
+ from llm.common import LlmParams, LlmApi
6
+
7
+
8
+ class LlmApi(LlmApi):
9
+ """
10
+ Класс для работы с API vllm.
11
+ """
12
+
13
+ def __init__(self, params: LlmParams):
14
+ super().__init__()
15
+ super().set_params(params)
16
+
17
+ async def get_models(self) -> List[str]:
18
+ """
19
+ Выполняет GET-запрос к API для получения списка доступных моделей.
20
+
21
+ Возвращает:
22
+ list[str]: Список идентификаторов моделей.
23
+ Если произошла ошибка или данные недоступны, возвращается пустой список.
24
+
25
+ Исключения:
26
+ Все ошибки HTTP-запросов логируются в консоль, но не выбрасываются дальше.
27
+ """
28
+ try:
29
+ async with httpx.AsyncClient() as client:
30
+ response = await client.get(f"{self.params.url}/v1/models", headers=super().create_headers())
31
+ if response.status_code == 200:
32
+ json_data = response.json()
33
+ return [item['id'] for item in json_data.get('data', [])]
34
+ except httpx.RequestError as error:
35
+ print('Error fetching models:', error)
36
+ return []
37
+
38
+ async def get_model(self) -> str:
39
+ model = None
40
+ if self.params.model is not None:
41
+ model = self.params.model
42
+ else:
43
+ models = await self.get_models()
44
+ model = models[0] if models else None
45
+
46
+ if model is None:
47
+ raise Exception("No model name provided and no models available.")
48
+
49
+ return model
50
+
51
+ def create_messages(self, prompt: str) -> List[dict]:
52
+ """
53
+ Создает сообщения для LLM на основе переданного промпта и системного промпта (если он задан).
54
+
55
+ Args:
56
+ prompt (str): Пользовательский промпт.
57
+
58
+ Returns:
59
+ list[dict]: Список сообщений с ролями и содержимым.
60
+ """
61
+ actual_prompt = self.apply_llm_template_to_prompt(prompt)
62
+ messages = []
63
+ if self.params.predict_params and self.params.predict_params.system_prompt:
64
+ messages.append({"role": "system", "content": self.params.predict_params.system_prompt})
65
+ messages.append({"role": "user", "content": actual_prompt})
66
+ return messages
67
+
68
+ def apply_llm_template_to_prompt(self, prompt: str) -> str:
69
+ """
70
+ Применяет шаблон LLM к переданному промпту, если он задан.
71
+
72
+ Args:
73
+ prompt (str): Пользовательский промпт.
74
+
75
+ Returns:
76
+ str: Промпт с примененным шаблоном (или оригинальный, если шаблон отсутствует).
77
+ """
78
+ actual_prompt = prompt
79
+ if self.params.template is not None:
80
+ actual_prompt = self.params.template.replace("{{PROMPT}}", actual_prompt)
81
+ return actual_prompt
82
+
83
+ async def tokenize(self, prompt: str) -> Optional[dict]:
84
+ """
85
+ Выполняет токенизацию переданного промпта.
86
+
87
+ Args:
88
+ prompt (str): Промпт для токенизации.
89
+
90
+ Returns:
91
+ Optional[dict]: Словарь с токенами и максимальной длиной модели, если запрос успешен.
92
+ Если запрос неуспешен, возвращает None.
93
+ """
94
+
95
+ actual_prompt = self.apply_llm_template_to_prompt(prompt)
96
+ request_data = {
97
+ "model": self.get_model(),
98
+ "prompt": actual_prompt,
99
+ "add_special_tokens": False,
100
+ }
101
+
102
+ try:
103
+ async with httpx.AsyncClient() as client:
104
+ response = await client.post(
105
+ f"{self.params.url}/tokenize",
106
+ json=request_data,
107
+ headers=super().create_headers(),
108
+ )
109
+ if response.status_code == 200:
110
+ data = response.json()
111
+ if "tokens" in data:
112
+ return {"tokens": data["tokens"], "maxLength": data.get("max_model_len")}
113
+ elif response.status_code == 404:
114
+ print("Tokenization endpoint not found (404).")
115
+ else:
116
+ print(f"Failed to tokenize: {response.status_code}")
117
+ except httpx.RequestError as e:
118
+ print(f"Request failed: {e}")
119
+
120
+ return None
121
+
122
+ async def detokenize(self, tokens: List[int]) -> Optional[str]:
123
+ """
124
+ Выполняет детокенизацию переданных токенов.
125
+
126
+ Args:
127
+ tokens (List[int]): Список токенов для детокенизации.
128
+
129
+ Returns:
130
+ Optional[str]: Строка, полученная в результате детокенизации, если запрос успешен.
131
+ Если запрос неуспешен, возвращает None.
132
+ """
133
+
134
+ request_data = {"model": self.get_model(), "tokens": tokens or []}
135
+
136
+ try:
137
+ async with httpx.AsyncClient() as client:
138
+ response = await client.post(
139
+ f"{self.params.url}/detokenize",
140
+ json=request_data,
141
+ headers=super().create_headers(),
142
+ )
143
+ if response.status_code == 200:
144
+ data = response.json()
145
+ if "prompt" in data:
146
+ return data["prompt"].strip()
147
+ elif response.status_code == 404:
148
+ print("Detokenization endpoint not found (404).")
149
+ else:
150
+ print(f"Failed to detokenize: {response.status_code}")
151
+ except httpx.RequestError as e:
152
+ print(f"Request failed: {e}")
153
+
154
+ return None
155
+
156
+ async def create_request(self, prompt: str) -> dict:
157
+ """
158
+ Создает запрос для предсказания на основе параметров LLM.
159
+
160
+ Args:
161
+ prompt (str): Промпт для запроса.
162
+
163
+ Returns:
164
+ dict: Словарь с параметрами для выполнения запроса.
165
+ """
166
+ model = self.get_model()
167
+
168
+ request = {
169
+ "stream": True,
170
+ "model": model,
171
+ }
172
+
173
+ predict_params = self.params.predict_params
174
+ if predict_params:
175
+ if predict_params.stop:
176
+ non_empty_stop = list(filter(lambda o: o != "", predict_params.stop))
177
+ if non_empty_stop:
178
+ request["stop"] = non_empty_stop
179
+
180
+ if predict_params.n_predict is not None:
181
+ request["max_tokens"] = int(predict_params.n_predict or 0)
182
+
183
+ request["temperature"] = float(predict_params.temperature or 0)
184
+ if predict_params.top_k is not None:
185
+ request["top_k"] = int(predict_params.top_k)
186
+
187
+ if predict_params.top_p is not None:
188
+ request["top_p"] = float(predict_params.top_p)
189
+
190
+ if predict_params.min_p is not None:
191
+ request["min_p"] = float(predict_params.min_p)
192
+
193
+ if predict_params.seed is not None:
194
+ request["seed"] = int(predict_params.seed)
195
+
196
+ if predict_params.n_keep is not None:
197
+ request["n_keep"] = int(predict_params.n_keep)
198
+
199
+ if predict_params.cache_prompt is not None:
200
+ request["cache_prompt"] = bool(predict_params.cache_prompt)
201
+
202
+ if predict_params.repeat_penalty is not None:
203
+ request["repetition_penalty"] = float(predict_params.repeat_penalty)
204
+
205
+ if predict_params.repeat_last_n is not None:
206
+ request["repeat_last_n"] = int(predict_params.repeat_last_n)
207
+
208
+ if predict_params.presence_penalty is not None:
209
+ request["presence_penalty"] = float(predict_params.presence_penalty)
210
+
211
+ if predict_params.frequency_penalty is not None:
212
+ request["frequency_penalty"] = float(predict_params.frequency_penalty)
213
+
214
+ request["messages"] = self.create_messages(prompt)
215
+ return request
216
+
217
+ async def trim_sources(self, sources: str, user_request: str, system_prompt: str = None) -> dict:
218
+ """
219
+ Обрезает текст источников, чтобы уложиться в допустимое количество токенов.
220
+
221
+ Args:
222
+ sources (str): Текст источников.
223
+ user_request (str): Запрос пользователя с примененным шаблоном без текста источников.
224
+ system_prompt (str): Системный промпт, если нужен.
225
+
226
+ Returns:
227
+ dict: Словарь с результатом, количеством токенов до и после обрезки.
228
+ """
229
+ # Токенизация текста источников
230
+ sources_tokens_data = await self.tokenize(sources)
231
+ if sources_tokens_data is None:
232
+ raise ValueError("Failed to tokenize sources.")
233
+ max_token_count = sources_tokens_data.get("maxLength", 0)
234
+
235
+ # Токены системного промпта
236
+ system_prompt_token_count = 0
237
+
238
+ if system_prompt is not None:
239
+ system_prompt_tokens = await self.tokenize(system_prompt)
240
+ system_prompt_token_count = len(system_prompt_tokens["tokens"]) if system_prompt_tokens else 0
241
+
242
+ # Оригинальное количество токенов
243
+ original_token_count = len(sources_tokens_data["tokens"])
244
+
245
+ # Токенизация пользовательского промпта
246
+ aux_prompt = self.apply_llm_template_to_prompt(user_request)
247
+ aux_tokens_data = await self.tokenize(aux_prompt)
248
+
249
+ aux_token_count = len(aux_tokens_data["tokens"]) if aux_tokens_data else 0
250
+
251
+ # Максимально допустимое количество токенов для источников
252
+ max_length = (
253
+ max_token_count
254
+ - (self.params.predict_params.n_predict or 0)
255
+ - aux_token_count
256
+ - system_prompt_token_count
257
+ )
258
+ max_length = max(max_length, 0)
259
+
260
+ # Обрезка токенов источников
261
+ if "tokens" in sources_tokens_data:
262
+ sources_tokens_data["tokens"] = sources_tokens_data["tokens"][:max_length]
263
+ detokenized_prompt = await self.detokenize(sources_tokens_data["tokens"])
264
+ if detokenized_prompt is not None:
265
+ sources = detokenized_prompt
266
+ else:
267
+ sources = sources[:max_length]
268
+ else:
269
+ sources = sources[:max_length]
270
+
271
+ # Возврат результата
272
+ return {
273
+ "result": sources,
274
+ "originalTokenCount": original_token_count,
275
+ "slicedTokenCount": len(sources_tokens_data["tokens"]),
276
+ }
277
+
278
+ async def predict(self, prompt: str) -> str:
279
+ """
280
+ Выполняет запрос к API с поддержкой потокового вывода (SSE) и возвращает результат.
281
+
282
+ Args:
283
+ prompt (str): Входной текст для предсказания.
284
+
285
+ Returns:
286
+ str: Сгенерированный текст.
287
+ """
288
+ async with httpx.AsyncClient() as client:
289
+ # Формируем тело запроса
290
+ request = await self.create_request(prompt)
291
+
292
+ # Начинаем потоковый запрос
293
+ async with client.stream("POST", f"{self.params.url}/v1/chat/completions", json=request) as response:
294
+ if response.status_code != 200:
295
+ # Если ошибка, читаем ответ для получения подробностей
296
+ error_content = await response.aread()
297
+ raise Exception(f"API error: {error_content.decode('utf-8')}")
298
+
299
+ # Для хранения результата
300
+ generated_text = ""
301
+
302
+ # Асинхронное чтение построчно
303
+ async for line in response.aiter_lines():
304
+ if line.startswith("data: "): # SSE-сообщения начинаются с "data: "
305
+ try:
306
+ # Парсим JSON из строки
307
+ data = json.loads(line[len("data: "):].strip())
308
+ if data == "[DONE]": # Конец потока
309
+ break
310
+ if "choices" in data and data["choices"]:
311
+ # Получаем текст из текущего токена
312
+ token_value = data["choices"][0].get("delta", {}).get("content", "")
313
+ generated_text += token_value
314
+ except json.JSONDecodeError:
315
+ continue # Игнорируем строки, которые не удается декодировать
316
+
317
+ return generated_text.strip()
requirements CHANGED
@@ -1,4 +1,3 @@
1
  flask
2
  flask-cors
3
- huggingface_hub
4
- requests
 
1
  flask
2
  flask-cors
3
+ python-dotenv