Spaces:
Sleeping
Sleeping
update
Browse files- llm/common.py +0 -1
- llm/deepinfra_api.py +37 -0
- llm/llm_api.py +37 -0
- main.py +17 -27
- models/text_request.py +5 -0
- prompts/getsummary.py +36 -0
- routes/summary.py +17 -0
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 |
-
|
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 |
-
|
45 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"]) -
|
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}
|