Merge pull request 'compilation' (#1) from compilation into main
Reviewed-on: http://192.168.3.3:3030/drholy/llm/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
@@ -28,7 +28,7 @@ RUN pip install --upgrade pip && \
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Копируем только requirements.txt для кэширования слоя
|
# Копируем только requirements.txt для кэширования слоя
|
||||||
COPY offline_packages/test.txt /app/requirements.txt
|
COPY offline_packages/requirements.txt /app/requirements.txt
|
||||||
|
|
||||||
# Установка зависимостей (включая GPU-версии)
|
# Установка зависимостей (включая GPU-версии)
|
||||||
RUN pip install --no-cache-dir -r /app/requirements.txt
|
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||||
@@ -37,4 +37,4 @@ RUN pip install --no-cache-dir -r /app/requirements.txt
|
|||||||
COPY app /app
|
COPY app /app
|
||||||
|
|
||||||
# Запуск приложения
|
# Запуск приложения
|
||||||
#CMD ["python", "main.py"]
|
CMD ["python", "main.py"]
|
||||||
101
README.md
101
README.md
@@ -68,12 +68,7 @@ docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Установка системных зависимостей
|
## 3. Проверка наличия драйверов видеокарты
|
||||||
|
|
||||||
### Установка заголовков ядра Linux
|
|
||||||
```bash
|
|
||||||
apt install linux-headers-$(uname -r)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Проверка видеодрайверов
|
### Проверка видеодрайверов
|
||||||
```bash
|
```bash
|
||||||
@@ -133,8 +128,10 @@ git lfs install
|
|||||||
|
|
||||||
> ✅ Успешное клонирование:
|
> ✅ Успешное клонирование:
|
||||||
```bash
|
```bash
|
||||||
git clone https://huggingface.co/Qwen/Qwen3-8B
|
mkdir models/
|
||||||
cd Qwen3-8B/
|
cd models
|
||||||
|
git clone https://huggingface.co/Qwen/Qwen3-4B
|
||||||
|
git clone https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -244,6 +241,94 @@ history > history.md
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```bash
|
||||||
|
├── Dockerfile
|
||||||
|
├── README.md
|
||||||
|
├── app
|
||||||
|
│ ├── __pycache__
|
||||||
|
│ │ └── config.cpython-310.pyc
|
||||||
|
│ ├── config.py
|
||||||
|
│ ├── data
|
||||||
|
│ │ ├── chroma_db
|
||||||
|
│ │ │ ├── 55652433-5130-4082-b593-3dcf3ad2bc92
|
||||||
|
│ │ │ └── chroma.sqlite3
|
||||||
|
│ │ ├── docs
|
||||||
|
│ │ │ ├── REALITY.md
|
||||||
|
│ │ │ ├── VLESS-GRPC.md
|
||||||
|
│ │ │ ├── XTLS-Vision.md
|
||||||
|
│ │ │ └── marzban_ru.md
|
||||||
|
│ │ ├── generate_json_files.py
|
||||||
|
│ │ ├── generated_docs_db.py
|
||||||
|
│ │ └── parsed_json
|
||||||
|
│ │ ├── REALITY.json
|
||||||
|
│ │ ├── VLESS-GRPC.json
|
||||||
|
│ │ ├── XTLS-Vision.json
|
||||||
|
│ │ └── marzban_ru.json
|
||||||
|
│ ├── main.py
|
||||||
|
│ └── old_without_db.py
|
||||||
|
├── backup_docker_image.md
|
||||||
|
├── cache
|
||||||
|
│ ├── cache
|
||||||
|
│ │ ├── chroma
|
||||||
|
│ │ │ └── telemetry_user_id
|
||||||
|
│ │ └── pip
|
||||||
|
│ │ ├── http-v2
|
||||||
|
│ │ └── selfcheck
|
||||||
|
│ └── site-packages
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── history.md
|
||||||
|
├── models
|
||||||
|
│ ├── Qwen3-4B
|
||||||
|
│ │ ├── LICENSE
|
||||||
|
│ │ ├── README.md
|
||||||
|
│ │ ├── config.json
|
||||||
|
│ │ ├── generation_config.json
|
||||||
|
│ │ ├── merges.txt
|
||||||
|
│ │ ├── model-00001-of-00003.safetensors
|
||||||
|
│ │ ├── model-00002-of-00003.safetensors
|
||||||
|
│ │ ├── model-00003-of-00003.safetensors
|
||||||
|
│ │ ├── model.safetensors.index.json
|
||||||
|
│ │ ├── tokenizer.json
|
||||||
|
│ │ ├── tokenizer_config.json
|
||||||
|
│ │ └── vocab.json
|
||||||
|
│ └── paraphrase-multilingual-MiniLM-L12-v2
|
||||||
|
│ ├── 1_Pooling
|
||||||
|
│ │ └── config.json
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── config.json
|
||||||
|
│ ├── config_sentence_transformers.json
|
||||||
|
│ ├── model.safetensors
|
||||||
|
│ ├── modules.json
|
||||||
|
│ ├── onnx
|
||||||
|
│ │ ├── model.onnx
|
||||||
|
│ │ ├── model_O1.onnx
|
||||||
|
│ │ ├── model_O2.onnx
|
||||||
|
│ │ ├── model_O3.onnx
|
||||||
|
│ │ ├── model_O4.onnx
|
||||||
|
│ │ ├── model_qint8_arm64.onnx
|
||||||
|
│ │ ├── model_qint8_avx512.onnx
|
||||||
|
│ │ ├── model_qint8_avx512_vnni.onnx
|
||||||
|
│ │ └── model_quint8_avx2.onnx
|
||||||
|
│ ├── openvino
|
||||||
|
│ │ ├── openvino_model.bin
|
||||||
|
│ │ ├── openvino_model.xml
|
||||||
|
│ │ ├── openvino_model_qint8_quantized.bin
|
||||||
|
│ │ └── openvino_model_qint8_quantized.xml
|
||||||
|
│ ├── pytorch_model.bin
|
||||||
|
│ ├── sentence_bert_config.json
|
||||||
|
│ ├── sentencepiece.bpe.model
|
||||||
|
│ ├── special_tokens_map.json
|
||||||
|
│ ├── tf_model.h5
|
||||||
|
│ ├── tokenizer.json
|
||||||
|
│ ├── tokenizer_config.json
|
||||||
|
│ └── unigram.json
|
||||||
|
└── offline_packages
|
||||||
|
└── requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Выводы
|
## Выводы
|
||||||
|
|
||||||
Был успешно выполнен полный цикл:
|
Был успешно выполнен полный цикл:
|
||||||
|
|||||||
228
app/main.py
228
app/main.py
@@ -1,77 +1,175 @@
|
|||||||
|
from typing import Any, Dict, List, Optional
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
|
||||||
import torch
|
import torch
|
||||||
import threading
|
import threading
|
||||||
|
from loguru import logger
|
||||||
|
from langchain_chroma import Chroma
|
||||||
|
from langchain_huggingface import HuggingFaceEmbeddings
|
||||||
|
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
||||||
|
|
||||||
# Пути к модели
|
# Предполагается, что у тебя есть config.py с settings
|
||||||
model_name = "/models/Qwen3-8B"
|
from config import settings
|
||||||
|
|
||||||
# Загрузка токенизатора и модели (один раз при старте)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(
|
|
||||||
model_name,
|
|
||||||
torch_dtype=torch.float16,
|
|
||||||
device_map="cuda",
|
|
||||||
trust_remote_code=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Отключим кэширование в истории, чтобы каждый запрос был независимым
|
class ChatWithAI:
|
||||||
def generate_response(message, history):
|
def __init__(self, provider: str = "qwen3"):
|
||||||
# Форматируем диалог: используем только текущую историю
|
self.provider = provider
|
||||||
prompt = ""
|
self.embeddings = HuggingFaceEmbeddings(
|
||||||
for human, assistant in history:
|
model_name=settings.LM_MODEL_NAME,
|
||||||
prompt += f"<|im_start|>user\n{human}<|im_end|>\n<|im_start|>assistant\n{assistant}<|im_end|>\n"
|
model_kwargs={"device": "cuda"},
|
||||||
prompt += f"<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
|
encode_kwargs={"normalize_embeddings": True},
|
||||||
|
)
|
||||||
|
|
||||||
# Токенизация
|
if provider == "qwen3":
|
||||||
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-4B")
|
||||||
|
logger.info(f"Загрузка локальной модели: {model_name}")
|
||||||
|
|
||||||
# Создаём уникальный streamer для каждого запроса
|
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||||
streamer = TextIteratorStreamer(
|
self.model = AutoModelForCausalLM.from_pretrained(
|
||||||
tokenizer,
|
model_name,
|
||||||
skip_prompt=True,
|
torch_dtype=torch.float32,
|
||||||
skip_special_tokens=True
|
device_map="cuda",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Streamer для потоковой генерации
|
||||||
|
self.streamer = TextIteratorStreamer(
|
||||||
|
self.tokenizer,
|
||||||
|
skip_prompt=True,
|
||||||
|
skip_special_tokens=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Неподдерживаемый провайдер: {provider}")
|
||||||
|
|
||||||
|
self.chroma_db = Chroma(
|
||||||
|
persist_directory=settings.DOCS_CHROMA_PATH,
|
||||||
|
embedding_function=self.embeddings,
|
||||||
|
collection_name=settings.DOCS_COLLECTION_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_relevant_context(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение релевантного контекста из базы данных."""
|
||||||
|
try:
|
||||||
|
results = self.chroma_db.similarity_search(query, k=k)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"text": doc.page_content,
|
||||||
|
"metadata": doc.metadata,
|
||||||
|
}
|
||||||
|
for doc in results
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении контекста: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def format_context(self, context: List[Dict[str, Any]]) -> str:
|
||||||
|
"""Форматирование контекста для промпта."""
|
||||||
|
formatted_context = []
|
||||||
|
for item in context:
|
||||||
|
metadata_str = "\n".join(f"{k}: {v}" for k, v in item["metadata"].items())
|
||||||
|
formatted_context.append(
|
||||||
|
f"Текст: {item['text']}\nМетаданные:\n{metadata_str}\n"
|
||||||
|
)
|
||||||
|
return "\n---\n".join(formatted_context)
|
||||||
|
|
||||||
|
def generate_response_stream(self, query: str):
|
||||||
|
"""Генерация ответа с потоковой передачей токенов."""
|
||||||
|
try:
|
||||||
|
logger.info(f"Пользовательский запрос: {query}")
|
||||||
|
context = self.get_relevant_context(query)
|
||||||
|
if not context:
|
||||||
|
yield "Извините, не удалось найти релевантный контекст для ответа."
|
||||||
|
return
|
||||||
|
|
||||||
|
formatted_context = self.format_context(context)
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": """Ты — внутренний менеджер компании Mazban. Отвечаешь по делу без лишних вступлений.
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
1. Сразу переходи к сути, без фраз типа "На основе контекста"
|
||||||
|
2. Используй только факты. Если точных данных нет — отвечай общими фразами об Marzban, но не придумывай конкретику
|
||||||
|
3. Используй обычный текст без форматирования
|
||||||
|
4. Включай ссылки только если они есть в контексте
|
||||||
|
5. Говори от первого лица множественного числа: "Мы предоставляем", "У нас есть"
|
||||||
|
6. При упоминании файлов делай это естественно, например: "Я прикреплю инструкцию, где подробно описаны шаги"
|
||||||
|
7. На приветствия отвечай доброжелательно, на негатив — с легким юмором
|
||||||
|
8. Можешь при ответах использовать общую информацию из открытых источников по Marzban, но опирайся на контекст
|
||||||
|
9. Если пользователь спрашивает о ценах, планах или технических характеристиках — давай конкретные ответы из контекста
|
||||||
|
10. При технических вопросах предлагай практические решения
|
||||||
|
|
||||||
|
Персонализируй ответы, упоминая имя клиента если оно есть в контексте. Будь краток, информативен и полезен.""",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": f"Вопрос: {query}\nКонтекст: {formatted_context}",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Применяем шаблон чата
|
||||||
|
prompt = self.tokenizer.apply_chat_template(
|
||||||
|
messages,
|
||||||
|
tokenize=False,
|
||||||
|
add_generation_prompt=True,
|
||||||
|
enable_thinking=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Подготавливаем вход
|
||||||
|
inputs = self.tokenizer(prompt, return_tensors="pt").to("cuda")
|
||||||
|
|
||||||
|
# Очищаем streamer и запускаем генерацию в отдельном потоке
|
||||||
|
self.streamer = TextIteratorStreamer(
|
||||||
|
self.tokenizer,
|
||||||
|
skip_prompt=True,
|
||||||
|
skip_special_tokens=True
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_kwargs = {
|
||||||
|
"input_ids": inputs["input_ids"],
|
||||||
|
"max_new_tokens": 512,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"do_sample": True,
|
||||||
|
"top_p": 0.9,
|
||||||
|
"pad_token_id": self.tokenizer.eos_token_id,
|
||||||
|
"streamer": self.streamer,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = threading.Thread(target=self.model.generate, kwargs=generate_kwargs)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# Потоковая передача токенов
|
||||||
|
buffer = ""
|
||||||
|
for token in self.streamer:
|
||||||
|
buffer += token
|
||||||
|
yield buffer # Отправляем частичный ответ
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при генерации ответа: {e}")
|
||||||
|
yield "Произошла ошибка при генерации ответа."
|
||||||
|
|
||||||
|
|
||||||
|
# === Gradio интерфейс ===
|
||||||
|
def main():
|
||||||
|
chat = ChatWithAI(provider="qwen3")
|
||||||
|
|
||||||
|
def respond(message, history):
|
||||||
|
# Генерируем ответ по частям
|
||||||
|
for token in chat.generate_response_stream(message):
|
||||||
|
yield token
|
||||||
|
|
||||||
|
demo = gr.ChatInterface(
|
||||||
|
fn=respond,
|
||||||
|
title="Помощник настройки Marzban",
|
||||||
|
description="Задайте вопрос — получите ответ от внутреннего менеджера.",
|
||||||
|
examples=[
|
||||||
|
"Как подключить Marzban?",
|
||||||
|
"Как настроить telegram бота?",
|
||||||
|
"Что такое marzban?"
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
demo.launch(server_name="0.0.0.0", server_port=8080, share=False)
|
||||||
|
|
||||||
# Параметры генерации
|
|
||||||
generation_kwargs = {
|
|
||||||
"input_ids": inputs["input_ids"],
|
|
||||||
"max_new_tokens": 1024,
|
|
||||||
"temperature": 0.6,
|
|
||||||
"top_p": 0.9,
|
|
||||||
"do_sample": True,
|
|
||||||
"pad_token_id": tokenizer.eos_token_id,
|
|
||||||
"streamer": streamer,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Запускаем генерацию в отдельном потоке
|
|
||||||
thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# Постепенно возвращаем результат
|
|
||||||
buffer = ""
|
|
||||||
for new_text in streamer:
|
|
||||||
buffer += new_text
|
|
||||||
yield buffer.strip()
|
|
||||||
|
|
||||||
# Создаем интерфейс
|
|
||||||
demo = gr.ChatInterface(
|
|
||||||
fn=generate_response,
|
|
||||||
title="Qwen3-4B-Base Chat",
|
|
||||||
description="Общайтесь с моделью Qwen3-4B-Base в режиме реального времени с потоковой генерацией",
|
|
||||||
examples=[
|
|
||||||
"Объясни, как работает квантование AWQ?",
|
|
||||||
"Напиши стихотворение про ИИ",
|
|
||||||
"Какие преимущества у Qwen3 перед предыдущими версиями?"
|
|
||||||
],
|
|
||||||
theme="soft",
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# ВАЖНО: используем .queue() для поддержки асинхронной обработки
|
main()
|
||||||
demo.queue(max_size=20, default_concurrency_limit=10).launch(
|
|
||||||
server_port=8080,
|
|
||||||
server_name="0.0.0.0",
|
|
||||||
share=False,
|
|
||||||
# Можно добавить: max_batch_size=1, concurrency_count=4
|
|
||||||
)
|
|
||||||
77
app/old_without_db.py
Normal file
77
app/old_without_db.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import gradio as gr
|
||||||
|
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
||||||
|
import torch
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# Пути к модели
|
||||||
|
model_name = "/models/Qwen3-8B"
|
||||||
|
|
||||||
|
# Загрузка токенизатора и модели (один раз при старте)
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
||||||
|
model = AutoModelForCausalLM.from_pretrained(
|
||||||
|
model_name,
|
||||||
|
torch_dtype=torch.float16,
|
||||||
|
device_map="cuda",
|
||||||
|
trust_remote_code=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Отключим кэширование в истории, чтобы каждый запрос был независимым
|
||||||
|
def generate_response(message, history):
|
||||||
|
# Форматируем диалог: используем только текущую историю
|
||||||
|
prompt = ""
|
||||||
|
for human, assistant in history:
|
||||||
|
prompt += f"<|im_start|>user\n{human}<|im_end|>\n<|im_start|>assistant\n{assistant}<|im_end|>\n"
|
||||||
|
prompt += f"<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
|
||||||
|
|
||||||
|
# Токенизация
|
||||||
|
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
||||||
|
|
||||||
|
# Создаём уникальный streamer для каждого запроса
|
||||||
|
streamer = TextIteratorStreamer(
|
||||||
|
tokenizer,
|
||||||
|
skip_prompt=True,
|
||||||
|
skip_special_tokens=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Параметры генерации
|
||||||
|
generation_kwargs = {
|
||||||
|
"input_ids": inputs["input_ids"],
|
||||||
|
"max_new_tokens": 1024,
|
||||||
|
"temperature": 0.6,
|
||||||
|
"top_p": 0.9,
|
||||||
|
"do_sample": True,
|
||||||
|
"pad_token_id": tokenizer.eos_token_id,
|
||||||
|
"streamer": streamer,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запускаем генерацию в отдельном потоке
|
||||||
|
thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# Постепенно возвращаем результат
|
||||||
|
buffer = ""
|
||||||
|
for new_text in streamer:
|
||||||
|
buffer += new_text
|
||||||
|
yield buffer.strip()
|
||||||
|
|
||||||
|
# Создаем интерфейс
|
||||||
|
demo = gr.ChatInterface(
|
||||||
|
fn=generate_response,
|
||||||
|
title="Qwen3-4B-Base Chat",
|
||||||
|
description="Общайтесь с моделью Qwen3-4B-Base в режиме реального времени с потоковой генерацией",
|
||||||
|
examples=[
|
||||||
|
"Объясни, как работает квантование AWQ?",
|
||||||
|
"Напиши стихотворение про ИИ",
|
||||||
|
"Какие преимущества у Qwen3 перед предыдущими версиями?"
|
||||||
|
],
|
||||||
|
theme="soft",
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# ВАЖНО: используем .queue() для поддержки асинхронной обработки
|
||||||
|
demo.queue(max_size=20, default_concurrency_limit=10).launch(
|
||||||
|
server_port=8080,
|
||||||
|
server_name="0.0.0.0",
|
||||||
|
share=False,
|
||||||
|
# Можно добавить: max_batch_size=1, concurrency_count=4
|
||||||
|
)
|
||||||
161
app/test.py
161
app/test.py
@@ -1,161 +0,0 @@
|
|||||||
from typing import Any, Dict, List, Literal, Optional
|
|
||||||
|
|
||||||
import torch
|
|
||||||
from config import settings
|
|
||||||
from langchain_chroma import Chroma
|
|
||||||
from langchain_huggingface import HuggingFaceEmbeddings
|
|
||||||
from loguru import logger
|
|
||||||
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
|
|
||||||
|
|
||||||
|
|
||||||
class ChatWithAI:
|
|
||||||
def __init__(self, provider: "qwen3"):
|
|
||||||
self.provider = provider
|
|
||||||
self.embeddings = HuggingFaceEmbeddings(
|
|
||||||
model_name=settings.LM_MODEL_NAME,
|
|
||||||
model_kwargs={"device": "cuda"},
|
|
||||||
encode_kwargs={"normalize_embeddings": True},
|
|
||||||
)
|
|
||||||
|
|
||||||
if provider == "qwen3":
|
|
||||||
model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-4B")
|
|
||||||
|
|
||||||
logger.info(f"Загрузка локальной модели: {model_name}")
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(
|
|
||||||
model_name,
|
|
||||||
torch_dtype=torch.float32,
|
|
||||||
device_map="cuda",
|
|
||||||
)
|
|
||||||
|
|
||||||
#Создаём text-generation pipeline
|
|
||||||
self.llm = pipeline(
|
|
||||||
"text-generation",
|
|
||||||
model=model,
|
|
||||||
tokenizer=tokenizer,
|
|
||||||
device_map="cuda", # 0 = GPU, -1 = CPU
|
|
||||||
temperature=0.7,
|
|
||||||
max_new_tokens=512,
|
|
||||||
do_sample=True,
|
|
||||||
pad_token_id=tokenizer.eos_token_id,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Неподдерживаемый провайдер: {provider}")
|
|
||||||
|
|
||||||
self.chroma_db = Chroma(
|
|
||||||
persist_directory=settings.DOCS_CHROMA_PATH,
|
|
||||||
embedding_function=self.embeddings,
|
|
||||||
collection_name=settings.DOCS_COLLECTION_NAME,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_relevant_context(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
|
|
||||||
"""Получение релевантного контекста из базы данных."""
|
|
||||||
try:
|
|
||||||
results = self.chroma_db.similarity_search(query, k=k)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"text": doc.page_content,
|
|
||||||
"metadata": doc.metadata,
|
|
||||||
}
|
|
||||||
for doc in results
|
|
||||||
]
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при получении контекста: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def format_context(self, context: List[Dict[str, Any]]) -> str:
|
|
||||||
"""Форматирование контекста для промпта."""
|
|
||||||
formatted_context = []
|
|
||||||
for item in context:
|
|
||||||
metadata_str = "\n".join(f"{k}: {v}" for k, v in item["metadata"].items())
|
|
||||||
formatted_context.append(
|
|
||||||
f"Текст: {item['text']}\nМетаданные:\n{metadata_str}\n"
|
|
||||||
)
|
|
||||||
return "\n---\n".join(formatted_context)
|
|
||||||
|
|
||||||
def generate_response(self, query: str) -> Optional[str]:
|
|
||||||
"""Генерация ответа на основе запроса и контекста."""
|
|
||||||
try:
|
|
||||||
context = self.get_relevant_context(query)
|
|
||||||
if not context:
|
|
||||||
return "Извините, не удалось найти релевантный контекст для ответа."
|
|
||||||
|
|
||||||
formatted_context = self.format_context(context)
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
"content": """Ты — внутренний менеджер компании Amvera Cloud. Отвечаешь по делу без лишних вступлений.
|
|
||||||
|
|
||||||
Правила:
|
|
||||||
1. Сразу переходи к сути, без фраз типа "На основе контекста"
|
|
||||||
2. Используй только факты. Если точных данных нет — отвечай общими фразами об Marzban, но не придумывай конкретику
|
|
||||||
3. Используй обычный текст без форматирования
|
|
||||||
4. Включай ссылки только если они есть в контексте
|
|
||||||
5. Говори от первого лица множественного числа: "Мы предоставляем", "У нас есть"
|
|
||||||
6. При упоминании файлов делай это естественно, например: "Я прикреплю инструкцию, где подробно описаны шаги"
|
|
||||||
7. На приветствия отвечай доброжелательно, на негатив — с легким юмором
|
|
||||||
8. Можешь при ответах использовать общую информацию из открытых источников по Marzban, но опирайся на контекст
|
|
||||||
9. Если пользователь спрашивает о ценах, планах или технических характеристиках — давай конкретные ответы из контекста
|
|
||||||
10. При технических вопросах предлагай практические решения
|
|
||||||
|
|
||||||
Персонализируй ответы, упоминая имя клиента если оно есть в контексте. Будь краток, информативен и полезен.""",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": f"Вопрос: {query}\nКонтекст: {formatted_context}",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
# Генерация через transformers
|
|
||||||
if self.provider == "qwen3":
|
|
||||||
# Используем токенизатор модели для форматирования чата
|
|
||||||
tokenizer = self.llm.tokenizer
|
|
||||||
model = self.llm.model
|
|
||||||
|
|
||||||
# Применяем chat template (поддерживается в современных моделях: Zephyr, Llama3, Qwen и т.д.)
|
|
||||||
prompt = tokenizer.apply_chat_template(
|
|
||||||
messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Генерация
|
|
||||||
outputs = self.llm(
|
|
||||||
prompt,
|
|
||||||
max_new_tokens=512,
|
|
||||||
temperature=0.7,
|
|
||||||
do_sample=True,
|
|
||||||
top_p=0.9,
|
|
||||||
pad_token_id=tokenizer.eos_token_id,
|
|
||||||
)
|
|
||||||
response_text = outputs[0]["generated_text"]
|
|
||||||
|
|
||||||
# Убираем входной промпт, оставляем только ответ
|
|
||||||
if prompt in response_text:
|
|
||||||
response_text = response_text[len(prompt):].strip()
|
|
||||||
|
|
||||||
return response_text
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Остальные провайдеры (deepseek, openai) используют langchain
|
|
||||||
response = self.llm.invoke(messages)
|
|
||||||
if hasattr(response, "content"):
|
|
||||||
return str(response.content)
|
|
||||||
return str(response).strip()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка при генерации ответа: {e}")
|
|
||||||
return "Произошла ошибка при генерации ответа."
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
chat = ChatWithAI(provider="qwen3")
|
|
||||||
print("\n=== Чат с ИИ ===\n")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
query = input("Вы: ")
|
|
||||||
if query.lower() == "выход":
|
|
||||||
print("\nДо свидания!")
|
|
||||||
break
|
|
||||||
|
|
||||||
print("\nИИ печатает...", end="\r")
|
|
||||||
response = chat.generate_response(query)
|
|
||||||
print(" " * 20, end="\r") # Очищаем "ИИ печатает..."
|
|
||||||
print(f"ИИ: {response}\n")
|
|
||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
- ./cache/cache:/root/.cache
|
- ./cache/cache:/root/.cache
|
||||||
- ./cache/site-packages:/usr/local/lib/python3.10/site-packages
|
- ./cache/site-packages:/usr/local/lib/python3.10/site-packages
|
||||||
- ./offline_packages:/offline_packages
|
- ./offline_packages:/offline_packages
|
||||||
entrypoint: sleep 1000000 #./app/entrypoint.sh
|
# entrypoint: sleep 1000000 #./app/entrypoint.sh
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
42
install.sh
Normal file
42
install.sh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
apt-get update
|
||||||
|
apt-get install ca-certificates curl
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||||
|
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
|
echo \
|
||||||
|
'{ \
|
||||||
|
"runtimes": { \
|
||||||
|
"nvidia": { \
|
||||||
|
"path": "nvidia-container-runtime", \
|
||||||
|
"runtimeArgs": [] \
|
||||||
|
} \
|
||||||
|
}, \
|
||||||
|
"default-runtime": "nvidia" \
|
||||||
|
}' > /etc/docker/daemon.json
|
||||||
|
|
||||||
|
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \
|
||||||
|
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
|
||||||
|
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
|
||||||
|
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.17.8-1
|
||||||
|
apt-get install -y \
|
||||||
|
nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
|
||||||
|
nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
|
||||||
|
libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
|
||||||
|
libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
|
||||||
|
nvidia-container-runtime \
|
||||||
|
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
|
||||||
|
git git-lfs
|
||||||
|
|
||||||
|
git lfs install
|
||||||
|
|
||||||
|
lspci -k | grep -EA2 'VGA|3D'
|
||||||
|
nvidia-smi
|
||||||
Reference in New Issue
Block a user