September 26

🧩 Как самому быстро собрать RAG-систему на локальной модели с vLLM

🚀 Введение

Retrieval-Augmented Generation (RAG) — это подход, при котором LLM не «выдумывает» ответ, а опирается на собственные документы или, иными словами, использует внутреннюю "Базу Знаний".

В этой статье мы шаг за шагом соберём простую RAG-систему:

  • 📦 запустим локальную LLM через vLLM,
  • 🔍 подключим векторный поиск (FAISS),
  • 🧠 научим модель отвечать на основе собственных файлов.

1️⃣ Установка окружения

# Базовые зависимости
pip install vllm faiss-cpu transformers accelerate sentence-transformers

2️⃣ Запуск локальной модели через vLLM

Допустим, у нас есть модель Qwen2.5-7B-Instruct (путь нужно проверить и заменить на существующий).

python -m vllm.entrypoints.openai.api_server \
    --model ./models/Qwen2.5-7B-Instruct \
    --tensor-parallel-size 1 \
    --port 8000

Теперь крутится сервер, полностью совместимый с API OpenAI на:
👉 http://localhost:8000/v1/chat/completions


3️⃣ Подготовка документов

Пусть у нас есть папка docs/ с текстовыми файлами вида .txt:

import os
import faiss
from sentence_transformers import SentenceTransformer

# Модель для эмбеддингов (лёгкая и быстрая), можно подобрать другую
embedder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")

corpus, ids = [], []
for fname in os.listdir("docs"):
    if not fname.endswith(".txt"):  # например, работаем только с текстовыми файлами
        continue
    with open(f"docs/{fname}", "r", encoding="utf-8") as f:
        text = f.read()
        corpus.append(text)
        ids.append(fname)

# Векторизация документов
embeddings = embedder.encode(corpus, convert_to_numpy=True)

# Индекс FAISS
dim = embeddings.shape[1]
index = faiss.IndexFlatL2(dim)
index.add(embeddings)

print(f"📄 Загружено документов: {len(corpus)}")
print(f"📊 Размерность эмбеддингов: {dim}")

4️⃣ Запрос в стиле RAG

Теперь пишем функцию:

  1. Векторизуем вопрос.
  2. Находим релевантные документы через FAISS.
  3. Подставляем их в промпт для vLLM.
from openai import OpenAI

client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
CHAT_MODEL  = "Qwen2.5-7B-Instruct"          # для генерации ответов

def rag_query(question, top_k=2):
    # 1. Векторизация запроса
    q_emb = client.embeddings.create(
        model=EMBED_MODEL,
        input=question
    ).data[0].embedding
    
    # 2. Поиск ближайших документов
    D, I = index.search([q_emb], top_k)
    context = "\n\n".join([corpus[i] for i in I[0]])
    
    # 3. Промпт
    messages = [
        {"role": "system", "content": "Ты помощник, отвечай точно и по делу по загруженным данным."},
        {"role": "user", "content": f"Вопрос: {question}\n\nКонтекст:\n{context}"}
    ]
    
    # 4. Запрос к чатовой модели
    resp = client.chat.completions.create(
        model=CHAT_MODEL,
        messages=messages,
        temperature=0
    )
    
    return resp.choices[0].message.content

print(rag_query("Что такое accuracy?")))

5️⃣ Что получилось

Теперь есть в простом исполнении:

  • 🔍 поиск актуальной информации по загруженным документам,
  • 🤖 запущенная на своем сервере LLM, которая использует этот контекст,
  • 📡 всё работает локально без интернета (или в частном облаке) и без API-ключей.

🌍 Итог

  • Весь пайплайн умещается в ~50-100 строк кода.
  • Его можно расширить: добавить chunking, re-ranker, LangChain или LlamaIndex.
  • И самое главное: в таком подходе мы полностью контролируем данные и инфраструктуру.

✨ Попробуйте сами — и вы увидите, что собрать RAG-систему не сложнее, чем настроить обычный поиск. А если будут вопросы по ходу установки библиотек и запуска кода, то смело можно обращаться за помощью к хорошо известным большим языковым моделям: DeepSeek, Qwen, ChatGPT, Gemini, Grok или к дргим привычным.