muryshev commited on
Commit
9347ad1
·
1 Parent(s): 212f451
llm/common.py CHANGED
@@ -1,6 +1,5 @@
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
  """
 
1
  from pydantic import BaseModel, Field
2
  from typing import Optional, List, Protocol
 
3
 
4
  class LlmPredictParams(BaseModel):
5
  """
llm/deepinfra_api.py CHANGED
@@ -1,9 +1,15 @@
1
  import json
2
  from typing import Optional, List
3
  import httpx
 
4
  from transformers import AutoTokenizer
5
  from llm.common import LlmParams, LlmApi
6
 
 
 
 
 
 
7
  class DeepInfraApi(LlmApi):
8
  """
9
  Класс для работы с API vllm.
@@ -184,3 +190,34 @@ class DeepInfraApi(LlmApi):
184
  response = await client.post(f"{self.params.url}/v1/openai/chat/completions", headers=super().create_headers(), json=request, timeout=httpx.Timeout(connect=5.0, read=60.0, write=180, pool=10))
185
  if response.status_code == 200:
186
  return response.json()["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  from typing import Optional, List
3
  import httpx
4
+ import logging
5
  from transformers import AutoTokenizer
6
  from llm.common import LlmParams, LlmApi
7
 
8
+ logging.basicConfig(
9
+ level=logging.DEBUG,
10
+ format="%(asctime)s - %(message)s",
11
+ )
12
+
13
  class DeepInfraApi(LlmApi):
14
  """
15
  Класс для работы с API vllm.
 
190
  response = await client.post(f"{self.params.url}/v1/openai/chat/completions", headers=super().create_headers(), json=request, timeout=httpx.Timeout(connect=5.0, read=60.0, write=180, pool=10))
191
  if response.status_code == 200:
192
  return response.json()["choices"][0]["message"]["content"]
193
+ else:
194
+ logging.info(f"Request {prompt} failed: status code {response.status_code}")
195
+ logging.info(response.text)
196
+
197
+ async def trim_prompt(self, prompt: str, system_prompt: str = None):
198
+
199
+ result = await self.tokenize(prompt)
200
+
201
+ result_system = None
202
+ system_prompt_length = 0
203
+ if system_prompt is not None:
204
+ result_system = await self.tokenize(system_prompt)
205
+
206
+ if result_system is not None:
207
+ system_prompt_length = len(result_system["result"])
208
+
209
+
210
+ # в случае ошибки при токенизации, вернем исходную строку безопасной длины
211
+ if result["result"] is None or (system_prompt is not None and result_system is None):
212
+ return prompt[int(self.params.context_length / 3)]
213
+
214
+ #вероятно, часть уходит на форматирование чата, надо проверить
215
+ max_length = result["max_length"] - len(result["result"]) - system_prompt_length - self.params.predict_params.n_predict
216
+
217
+ detokenized_str = await self.detokenize(result["result"][:max_length])
218
+
219
+ # в случае ошибки при детокенизации, вернем исходную строку безопасной длины
220
+ if detokenized_str is None:
221
+ return prompt[self.params.context_length / 3]
222
+
223
+ return detokenized_str
llm/llm_api.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from threading import Lock
3
+ from llm.common import LlmParams, LlmPredictParams
4
+ from llm.deepinfra_api import DeepInfraApi
5
+
6
+ class LlmApi:
7
+ _instance = None
8
+ _lock = Lock()
9
+
10
+ def __new__(cls):
11
+ with cls._lock:
12
+ if cls._instance is None:
13
+ cls._instance = super(LlmApi, cls).__new__(cls)
14
+ cls._instance._initialize()
15
+ return cls._instance
16
+
17
+ def _initialize(self):
18
+ LLM_API_URL = os.getenv("LLM_API_URL", "https://api.deepinfra.com")
19
+ LLM_API_KEY = os.getenv("DEEPINFRA_API_KEY", "")
20
+ LLM_NAME = os.getenv("LLM_NAME", "meta-llama/Llama-3.3-70B-Instruct-Turbo")
21
+ TOKENIZER_NAME = os.getenv("TOKENIZER_NAME", "unsloth/Llama-3.3-70B-Instruct")
22
+
23
+ default_llm_params = LlmParams(
24
+ url=LLM_API_URL,
25
+ api_key=LLM_API_KEY,
26
+ model=LLM_NAME,
27
+ tokenizer=TOKENIZER_NAME,
28
+ context_length=130000,
29
+ predict_params=LlmPredictParams(
30
+ temperature=0.15, top_p=0.95, min_p=0.05, seed=42,
31
+ repetition_penalty=1.2, presence_penalty=1.1, n_predict=6000
32
+ )
33
+ )
34
+ self.api = DeepInfraApi(default_llm_params)
35
+
36
+ def get_api(self):
37
+ return self.api
main.py CHANGED
@@ -1,6 +1,5 @@
1
  from fastapi import FastAPI, Request, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
  import json
5
  import re
6
  import os
@@ -10,27 +9,15 @@ from llm import prompts
10
  from prompts import gettable
11
  from dotenv import load_dotenv
12
  import uvicorn
 
 
 
 
13
 
14
  # Загрузка переменных окружения из файла .env
15
  load_dotenv()
16
 
17
- LLM_API_URL = os.getenv("LLM_API_URL", "https://api.deepinfra.com")
18
- LLM_API_KEY = os.getenv("DEEPINFRA_API_KEY", "")
19
- LLM_NAME = os.getenv("LLM_NAME", "meta-llama/Llama-3.3-70B-Instruct-Turbo")
20
- TOKENIZER_NAME = os.getenv("TOKENIZER_NAME", "unsloth/Llama-3.3-70B-Instruct")
21
-
22
- default_llm_params = LlmParams(
23
- url=LLM_API_URL,
24
- api_key=LLM_API_KEY,
25
- model=LLM_NAME,
26
- tokenizer=TOKENIZER_NAME,
27
- context_length=130000,
28
- predict_params=LlmPredictParams(
29
- temperature=0.15, top_p=0.95, min_p=0.05, seed=42,
30
- repetition_penalty=1.2, presence_penalty=1.1, n_predict=6000
31
- )
32
- )
33
- llm_api = DeepInfraApi(default_llm_params)
34
 
35
  app = FastAPI()
36
 
@@ -41,21 +28,28 @@ app.add_middleware(
41
  allow_methods=["*"]
42
  )
43
 
44
- class TextRequest(BaseModel):
45
- text: str
46
- projects: list[str] = []
47
 
48
  async def trim_prompt(prompt: str, system_prompt: str):
49
 
50
  result = await llm_api.tokenize(prompt+system_prompt)
51
- result_system = await llm_api.tokenize(system_prompt)
 
 
 
 
 
 
 
 
52
 
53
  # в случае ошибки при токенизации, вернем исходную строку безопасной длины
54
  if result["result"] is None or result_system is None:
55
  return prompt[llm_api.params.context_length / 3]
56
 
57
  #вероятно, часть уходит на форматирование чата, надо проверить
58
- max_length = result["max_length"] - len(result["result"]) - len(result_system["result"]) - llm_api.params.predict_params.n_predict
59
 
60
  detokenized_str = await llm_api.detokenize(result["result"][:max_length])
61
 
@@ -133,10 +127,6 @@ async def generate_response(prompt):
133
  prompt = await trim_prompt(prompt)
134
  return await llm_api.predict(prompt)
135
 
136
- @app.post("/getsummary")
137
- async def getsummary_route(request: TextRequest):
138
- return {"result": await generate_response(prompts.GET_SUMMARY.format(text=request.text))}
139
-
140
  @app.post("/cleantext")
141
  async def cleantext_route(request: TextRequest):
142
  return {"result": await generate_response(prompts.CLEAN_TEXT.format(text=request.text))}
 
1
  from fastapi import FastAPI, Request, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
 
3
  import json
4
  import re
5
  import os
 
9
  from prompts import gettable
10
  from dotenv import load_dotenv
11
  import uvicorn
12
+ from models.text_request import TextRequest
13
+ from llm.llm_api import LlmApi
14
+ from routes.summary import router as summary_router
15
+
16
 
17
  # Загрузка переменных окружения из файла .env
18
  load_dotenv()
19
 
20
+ llm_api = LlmApi().get_api()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  app = FastAPI()
23
 
 
28
  allow_methods=["*"]
29
  )
30
 
31
+ app.include_router(summary_router)
32
+
 
33
 
34
  async def trim_prompt(prompt: str, system_prompt: str):
35
 
36
  result = await llm_api.tokenize(prompt+system_prompt)
37
+
38
+ result_system = None
39
+ system_prompt_length = 0
40
+ if system_prompt is not None:
41
+ result_system = await llm_api.tokenize(system_prompt)
42
+
43
+ if result_system is not None:
44
+ system_prompt_length = len(result_system["result"])
45
+
46
 
47
  # в случае ошибки при токенизации, вернем исходную строку безопасной длины
48
  if result["result"] is None or result_system is None:
49
  return prompt[llm_api.params.context_length / 3]
50
 
51
  #вероятно, часть уходит на форматирование чата, надо проверить
52
+ max_length = result["max_length"] - len(result["result"]) - system_prompt_length - llm_api.params.predict_params.n_predict
53
 
54
  detokenized_str = await llm_api.detokenize(result["result"][:max_length])
55
 
 
127
  prompt = await trim_prompt(prompt)
128
  return await llm_api.predict(prompt)
129
 
 
 
 
 
130
  @app.post("/cleantext")
131
  async def cleantext_route(request: TextRequest):
132
  return {"result": await generate_response(prompts.CLEAN_TEXT.format(text=request.text))}
models/text_request.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class TextRequest(BaseModel):
4
+ text: str
5
+ projects: list[str] = []
prompts/getsummary.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ USER_PROMPT = """Привет! Твоя задача – кратко и четко пересказать текст расшифровки аудио с речью одного или нескольких человек.
2
+ Входные данные:
3
+ Тебе будет предоставлен объемный текст. У меня нет времени читать его целиком, поэтому:
4
+ Самостоятельно выдели в тексте ключевые смыслы и идеи.
5
+ Представь их в виде пронумерованного списка.
6
+ Пример списка:.
7
+ 1. Документы находятся в обработке...
8
+ 2. Директор компании поручил устранить неполадки с кулером...
9
+ 3. Требуется подготовить письмо в проектную организацию для уточнения сроков...
10
+ И так далее.
11
+ Если в каком-то пункте встречаются подпункты, оформляй их с помощью знака - .
12
+ Например:
13
+ 4. Всем сотрудником компании необходимо в индивидуальном порядке приобрести:
14
+ - Теплые шапки
15
+ - Рукавицы
16
+ - Шерстяные носки
17
+ Избегай:
18
+ - Канцеляризмов и штампов
19
+ - Приветствий и прощаний
20
+ - Описательных и вводных конструкций
21
+ - Использования любых символов латинского алфавита, если они не являются частью цитаты одного из спикеров
22
+ Требования к формату:
23
+ Используй официально-деловой стиль.
24
+ Отвечай только на РУССКОМ языке.
25
+ Чем короче и информативнее получится итоговый список, тем лучше.
26
+ Каждый пункт списка должен быть максимально емким.
27
+ Пиши про вещи, которые происходили в рамках совещания, только в прошедшем времени.
28
+ Теперь, пожалуйста, обработай этот текст:
29
+ {text}"""
30
+
31
+ CONVERT_SUMMARY_TO_TEXT = """
32
+ Пожалуйста, преврати этот список тезисов в последовательный текст, излагающий суть совещания!
33
+ Постарайся не упустить ничего важного. Сохраняй официально-деловой стиль, не здоровайся и не прощайся.
34
+ Текст должен быть написан на русском языке.
35
+ Список тезисов:
36
+ {summary}"""
routes/summary.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from models.text_request import TextRequest
3
+ from prompts import getsummary
4
+ from llm.llm_api import LlmApi
5
+
6
+ router = APIRouter(prefix="", tags=["summary"])
7
+
8
+ @router.post("/getsummary")
9
+ async def getsummary_route(request: TextRequest):
10
+ llm_api = LlmApi().get_api()
11
+ trimmed_summary_prompt = await llm_api.trim_prompt(getsummary.USER_PROMPT.format(text=request.text))
12
+ response_summary = await llm_api.predict(trimmed_summary_prompt)
13
+
14
+ trimmed_convert_prompt = await llm_api.trim_prompt(getsummary.CONVERT_SUMMARY_TO_TEXT.format(summary=response_summary))
15
+ response_final = await llm_api.predict(trimmed_convert_prompt)
16
+
17
+ return {"result": response_final}