
Полная версия
ChatGPT на вашем ноутбуке: бесплатно, анонимно, навсегда
· bge-m3: мультиязычная модель, поддерживает более 100 языков, включая русский. Размер ~2.2 ГБ, размерность — 1024. Качество на русском высокое, но размер великоват для нашего лёгкого локального стека. Она будет медленнее работать на CPU и займёт много места в итоговой упаковке. Кроме того, для наших задач она избыточна: нам не нужна поддержка 100 языков.
Вердикт: качество отличное, но размер и ресурсоёмкость для локального «однокнопочного» решения слишком велики.
Multilingual-E5 (Microsoft/Intfloat)
Семейство multilingual-e5 — разработка исследователей из Microsoft и сообщества. Модели: multilingual-e5-small, multilingual-e5-base, multilingual-e5-large.
· multilingual-e5-small: размер ~470 МБ, размерность векторов — 384. Поддерживает более 90 языков, включая русский.
· multilingual-e5-base: размер ~1.1 ГБ, размерность — 768. Качество русского языка выше, чем у small.
· multilingual-e5-large: размер ~2.2 ГБ, размерность — 1024. Максимальное качество, но высокие требования.
Обе модели (small и base) показывают очень достойный русский язык, особенно в задачах поиска. Важный нюанс: для моделей E5 требуется добавлять префиксы к тексту — "query: " для поисковых запросов и "passage: " для индексируемых документов. Это улучшает качество, но добавляет немного кода.
Вердикт: multilingual-e5-base — очень сильный кандидат: хороший баланс размера (1.1 ГБ) и качества. multilingual-e5-small — компромисс для слабого железа.
Jina AI Embeddings
Компания Jina AI предлагает линейку jina-embeddings-v3, а также более ранние jina-embeddings-v2. Это мультиязычные модели, поддерживающие русский язык.
· jina-embeddings-v2-base-multilingual: размер ~550 МБ, размерность — 768. Поддерживает русский, английский, немецкий и ещё несколько языков.
· jina-embeddings-v3: новая модель с поддержкой task-specific embeddings (можно управлять размерностью), размер ~1.5 ГБ, качество сопоставимо с BGE-m3.
Особенность Jina — они хорошо работают с длинными документами (до 8192 токенов), что полезно для больших чанков.
Вердикт: jina-embeddings-v2-base-multilingual — компактная, быстрая, с хорошим русским языком. Достойный соперник для E5.
Выбор embedding-модели для RAG
bge-m3. Флагманская модель от BAAI с размерностью вектора 1024 и размером 2.2 ГБ. Отлично работает с русским языком, но для локального использования часто избыточна — высокое качество достигается ценой низкой скорости даже на GPU. На процессоре работает медленно, а занимаемый объём сопоставим с небольшой языковой моделью.
multilingual-e5-small. Самая лёгкая модель в семействе — всего 470 МБ и размерность вектора 384. Хорошо понимает русский язык, работает очень быстро даже на слабом процессоре. Идеальный выбор для Raspberry Pi, старых ноутбуков и ситуаций, когда скорость важнее точности поиска. Качество приемлемое для большинства домашних задач.
multilingual-e5-base. Золотая середина и наш выбор для книги. Занимает 1.1 ГБ, размерность вектора 768. Отличное качество русского языка при умеренном потреблении ресурсов. Скорость на процессоре средняя — индексация папки из сотни документов занимает минуты, а поиск по базе — миллисекунды. Рекомендуется для большинства пользователей.
multilingual-e5-large. Максимальное качество среди multilingual-e5. Размер 2.2 ГБ, размерность 1024. Русский язык на высшем уровне, но скорость низкая, а потребление памяти высокое. Имеет смысл использовать, если у вас мощный компьютер с GPU и вы работаете с очень большими объёмами документов, где важна каждая десятая процента точности поиска.
jina-embeddings-v2-base. Альтернатива от компании Jina AI. Весит 550 МБ при размерности 768 — легче чем e5-base при той же размерности. Особенность модели — поддержка длинных текстов до 8192 токенов, что вдвое больше стандартных 512. Хорошо подходит, если ваши документы длинные и вы не хотите резать их на мелкие чанки.
Практический совет: Начните с multilingual-e5-base — это оптимальный баланс качества и скорости для большинства сценариев. Если не хватает скорости — переключитесь на e5-small. Если не хватает качества — попробуйте e5-large или jina. Замена embedding-модели не требует переписывания кода — достаточно изменить одну строку при создании объекта Embedder.
Что мы выбираем для книги
Я выбираю intfloat/multilingual-e5-base. Почему:
· Качество русского языка — одно из лучших среди моделей такого размера. По тестам на русскоязычных бенчмарках она превосходит аналоги.
· Размер 1.1 ГБ — приемлемо для включения в дистрибутив нашего приложения. Модель не занимает гигабайты, оставляя место для основной LLM.
· Совместимость — работает из коробки с sentence-transformers, не требует GPU, стабильна на всех платформах.
· Поддержка префиксов — мы будем использовать "query: " и "passage: " для повышения точности поиска. Это небольшое усложнение, которое окупается качеством.
Если ваше железо очень слабое (8 ГБ ОЗУ и меньше), вы можете заменить её на multilingual-e5-small — вся остальная архитектура останется той же, поменяется только одна строка с именем модели.
Установка и тестирование
Нам понадобится библиотека sentence-transformers. Установим её вместе с зависимостями:
bash
pip install sentence-transformers
Теперь напишем код для загрузки модели и векторизации текста. Создайте файл embedder.py:
python
# embedder.py
from sentence_transformers import SentenceTransformer
class Embedder:
"""
Обёртка над sentence-transformers для удобной работы с эмбеддингами.
"""
def __init__(self, model_name: str = "intfloat/multilingual-e5-base"):
print(f"Загрузка embedding-модели {model_name}...")
self._model = SentenceTransformer(model_name)
self._dim = self._model.get_sentence_embedding_dimension()
print(f"Модель загружена, размерность векторов: {self._dim}")
@property
def dimension(self) -> int:
"""Размерность векторов."""
return self._dim
def embed_query(self, text: str) -> list:
"""Преобразует поисковый запрос в вектор."""
# Для E5 моделей рекомендуется добавлять префикс "query: "
return self._model.encode(f"query: {text}", normalize_embeddings=True).tolist()
def embed_documents(self, texts: list[str]) -> list[list]:
"""Преобразует список документов (чанков) в список векторов."""
# Для документов добавляется префикс "passage: "
prefixed = [f"passage: {t}" for t in texts]
return self._model.encode(prefixed, normalize_embeddings=True).tolist()
if __name__ == "__main__":
emb = Embedder()
# Тест: векторизуем русский запрос и несколько чанков
query_vec = emb.embed_query("Какие гарантийные обязательства?")
doc_vecs = emb.embed_documents([
"Гарантийный срок на оборудование составляет 12 месяцев.",
"Стороны обязуются уведомлять друг друга об изменении реквизитов.",
"В случае нарушения сроков оплаты начисляется пеня 0.1% за каждый день."
])
print(f"Запрос векторизован, размерность: {len(query_vec)}")
print(f"Документов векторизовано: {len(doc_vecs)}")
Запустим:
bash
python embedder.py
При первом запуске модель скачается (около 1 ГБ) и загрузится. Вы увидите:
text
Загрузка embedding-модели intfloat/multilingual-e5-base...
Модель загружена, размерность векторов: 768
Запрос векторизован, размерность: 768
Документов векторизовано: 3
Теперь мы умеем превращать любой текст в векторы. В следующей главе мы организуем их хранение и поиск с помощью ChromaDB.
Возможные проблемы сembedding-моделью
Модель не скачивается — ошибка сети. При первом создании объекта Embedder() библиотека sentence-transformers пытается скачать модель с Hugging Face. Если интернет недоступен, загрузка прервётся с ошибкой. Решение: скачайте модель заранее на машине с интернетом, скопируйте папку с файлами в models/multilingual-e5-base/ и укажите локальный путь в конструкторе. После этого модель будет загружаться офлайн.
Ошибка нехватки памяти при загрузке. Embedding-модель загружается в оперативную память целиком. multilingual-e5-base требует около 1.1 ГБ, что обычно не проблема. Но если у вас компьютер с 4 ГБ ОЗУ или одновременно запущена языковая модель, памяти может не хватить. Переключитесь на multilingual-e5-small — она весит 470 МБ, вдвое легче, и при этом всё ещё хорошо понимает русский язык.
Векторы для похожих фраз получаются разными. Модель чувствительна к префиксам. Для поисковых запросов нужно добавлять "query: ", а для индексируемых документов — "passage: ". Если перепутать префиксы или забыть их, векторы будут неинформативными, и поиск перестанет работать. Также убедитесь, что включена нормализация — в sentence-transformers она обычно включена по умолчанию при использовании косинусной метрики.
Медленная векторизация на процессоре. Индексация сотни документов на CPU может занять несколько минут — это нормально. Для ускорения можно передать device="cuda" в конструктор SentenceTransformer, тогда векторизация будет использовать видеокарту и ускорится в разы. Однако для большинства домашних задач CPU-скорости вполне достаточно, а экономия видеопамяти важнее.
Что дальше
У нас есть embedding-модель, готовая превращать текст в векторы. Теперь нужно куда-то эти векторы сохранять и уметь быстро искать среди них ближайшие. Этим займёмся в главе 4.3 «Векторная база данных на коленке: ChromaDB и LanceDB».
Глава 4.3. Векторнаябаза данных на коленке: ChromaDB и LanceDB
Мы научились превращать текст в векторы. Теперь нам нужно их где-то хранить и, главное, уметь быстро находить векторы, ближайшие к запросу. Для этого существуют векторные базы данных — специализированные хранилища, заточенные под поиск по смысловой близости.
В облачном мире есть Pinecone, Weaviate, Qdrant. Они мощные, но многие требуют подключения к интернету или отдельного сервера. Нам же нужна база данных, которая:
· Работает полностью локально, как обычная папка с файлами.
· Не требует отдельного серверного процесса (никаких Docker, systemd).
· Умеет искать быстро — миллисекунды на запрос.
· Поддерживает русский язык (точнее, векторы, сгенерированные нашей embedding-моделью).
· Бесплатна и open-source.
В 2026 году есть два идеальных кандидата под эти требования: ChromaDB и LanceDB. Сравним их и выберем тот, на котором построим наш RAG.
ChromaDB: простота и Python-first
ChromaDB — векторная база данных, написанная с прицелом на простоту и использование из Python. Она хранит данные в файлах на диске, запускается как библиотека внутри вашего процесса и не требует никакой настройки.
Установка:
python
import chromadb
# Создаём клиент (данные хранятся в папке ./chroma_data)
client = chromadb.PersistentClient(path="./chroma_data")
# Создаём коллекцию (аналог таблицы в SQL)
collection = client.get_or_create_collection(
name="my_docs",
metadata={"hnsw:space": "cosine"} # метрика близости — косинусное расстояние
)
# Добавляем векторы с текстами и метаданными
collection.add(
embeddings=[[0.1, 0.2, ...], [0.3, 0.4, ...]], # векторы
documents=["Текст первого чанка", "Текст второго чанка"],
metadatas=[{"source": "file1.pdf", "page": 1}, {"source": "file2.docx"}],
ids=["doc1_chunk0", "doc2_chunk0"]
)
# Ищем ближайшие к запросу
results = collection.query(
query_embeddings=[[0.15, 0.25, ...]],
n_results=3
)
Базовое использование:
Плюсы ChromaDB:
· Максимальная простота. Установка в одну команду, API интуитивно понятный.
· Никакого сервера. База данных — это просто папка с файлами. PersistentClient работает синхронно, в том же процессе Python.
· Метаданные. Можно хранить вместе с вектором текст документа, источник, номер страницы и любые другие поля. Очень удобно для показа цитат.
· Встроенная поддержка embedding-функций. ChromaDB умеет сама вызывать sentence-transformers (но мы будем использовать свой Embedder для контроля).
· Активное сообщество. ChromaDB быстро развивается, много примеров и документации.
Минусы:
· Зависимость от SQLite3. Под капотом ChromaDB использует SQLite для хранения метаданных. Это надёжно, но накладывает ограничения на параллельную запись.
· Потребление памяти. При больших коллекциях (сотни тысяч чанков) может требовать значительной ОЗУ для индекса HNSW.
LanceDB: быстрая и легковесная альтернатива
LanceDB — векторная база данных нового поколения, построенная на колоночном формате хранения Lance. Она заточена под минимальное потребление памяти и высокую скорость.
Установка:
bash
pip install lancedb
Базовое использование:
python
import lancedb
import pyarrow as pa
# Подключаемся к папке (аналог PersistentClient)
db = lancedb.connect("./lancedb_data")
# Создаём таблицу со схемой
schema = pa.schema([
("vector", pa.list_(pa.float32(), 768)),
("text", pa.string()),
("source", pa.string()),
])
table = db.create_table("my_docs", schema=schema, mode="overwrite")
# Добавляем данные
table.add([{
"vector": [0.1, 0.2, ...],
"text": "Текст чанка",
"source": "file1.pdf"
}])
# Ищем
results = table.search([0.15, 0.25, ...]).limit(3).to_list()
Плюсы LanceDB:
· Очень высокая скорость. LanceDB использует колоночное хранение и эффективный формат файлов. Поиск работает быстрее, чем в ChromaDB, особенно на больших объёмах.
· Низкое потребление памяти. Индекс строится лениво и занимает меньше ОЗУ.
· Поддержка Apache Arrow. Данные хранятся в формате Arrow, что упрощает интеграцию с дата-инженерными инструментами.
· Без сервера. Так же как и ChromaDB, LanceDB — это просто библиотека и файлы.
Минусы:
· Менее зрелая экосистема. Документация и примеров меньше, чем у ChromaDB.
· API менее «питонячий». Требуется работать со схемами PyArrow, что может быть непривычно для новичков.
· Меньше встроенных фич. Нет встроенной поддержки embedding-функций, метаданные нужно описывать вручную.
Сравнение ChromaDB и LanceDB
Установка. Обе библиотеки устанавливаются одной командой pip install. ChromaDB не требует дополнительных зависимостей для базового использования. LanceDB использует формат Apache Arrow и может попросить установить pyarrow отдельно.
Хранение данных. ChromaDB хранит векторы и метаданные в обычной папке, используя SQLite для метаданных и собственный формат для индексов. Это просто и надёжно — можно скопировать папку на другой компьютер, и база заработает. LanceDB использует колоночный формат Lance, оптимизированный для скоростного доступа к большим объёмам данных.
API и простота использования. ChromaDB спроектирована с прицелом на Python-разработчика: интуитивно понятные методы add, query, count. Метаданные можно передать как обычный словарь, не описывая схему заранее. LanceDB требует описания схемы через PyArrow — это мощнее и строже, но сложнее для начинающих.
Метаданные. В ChromaDB метаданные хранятся как произвольный JSON-словарь рядом с каждым вектором — можно добавить любые поля без изменения структуры базы. В LanceDB метаданные нужно объявлять как колонки в схеме при создании таблицы, и изменить схему потом сложнее. Для быстрого прототипирования подход ChromaDB удобнее.
Производительность. На коллекциях до ста тысяч документов обе библиотеки работают быстро, разница незаметна. ChromaDB может замедляться при очень больших объёмах из-за SQLite под капотом. LanceDB изначально проектировалась для высоких нагрузок и сохраняет скорость даже на миллионах записей.
Зрелость и сообщество. ChromaDB существует дольше, у неё обширная документация, множество примеров и ответов на Stack Overflow. LanceDB моложе и развивается стремительно, но информации пока меньше. Для книги мы выбрали ChromaDB именно из-за зрелости и простоты: читатель быстрее найдёт ответ на возникший вопрос.
Встроенные эмбеддинги. ChromaDB умеет автоматически векторизовать текст при добавлении — можно не использовать отдельный Embedder. Но мы в книге делаем это вручную для полного контроля. В LanceDB такой функции нет.
Поддержка операционных систем. Обе библиотеки кроссплатформенны и работают на Windows, macOS и Linux без ограничений.
Итог: ChromaDB — лучший выбор для старта и небольших проектов. LanceDB — когда вы упираетесь в производительность на больших данных. Для масштабов личной базы знаний разница несущественна.
Что мы выбираем для книги
Я выбираю ChromaDB. Вот почему:
1. Простота API. Нам не нужно отвлекаться на схемы PyArrow и ручное описание колонок. ChromaDB работает «из коробки» — меньше кода, меньше шансов на ошибку.
2. Гибкие метаданные. Мы сможем хранить рядом с вектором текст чанка, имя исходного файла, номер страницы, дату документа — всё, что нужно для показа цитат. В LanceDB для этого пришлось бы явно объявлять колонки.
3. Достаточная производительность. Для масштабов личной базы знаний (тысячи, десятки тысяч документов) ChromaDB работает быстро, и разница с LanceDB будет незаметна.
4. Активное сообщество и документация. Если вы столкнётесь с проблемой, ответ с большей вероятностью найдётся для ChromaDB.
Если в будущем вы решите масштабировать систему на сотни тысяч документов, миграция на LanceDB или Qdrant будет несложной — интерфейс поиска у всех векторных баз похож.
Практикум: включаем ChromaDB в наш проект
Создадим модуль vector_store.py, который будет отвечать за работу с базой:
python
# vector_store.py
import chromadb
from typing import List, Dict, Optional
class VectorStore:
"""
Обёртка над ChromaDB для хранения и поиска эмбеддингов документов.
"""
def __init__(self, collection_name: str = "my_documents", persist_path: str = "./chroma_data"):
self._client = chromadb.PersistentClient(path=persist_path)
self._collection = self._client.get_or_create_collection(
name=collection_name,
metadata={"hnsw:space": "cosine"}
)
def add_documents(
self,
ids: List[str],
embeddings: List[List[float]],
documents: List[str],
metadatas: Optional[List[Dict]] = None
):
"""Добавляет документы (чанки) в коллекцию."""
self._collection.add(
ids=ids,
embeddings=embeddings,
documents=documents,
metadatas=metadatas
)
def search(self, query_embedding: List[float], n_results: int = 5) -> Dict:
"""Ищет ближайшие документы по вектору запроса."""
return self._collection.query(
query_embeddings=[query_embedding],
n_results=n_results
)
def count(self) -> int:
"""Возвращает количество документов в коллекции."""
return self._collection.count()
def clear(self):
"""Очищает коллекцию (полезно для переиндексации)."""
# Удаляем и создаём заново
name = self._collection.name
self._client.delete_collection(name)
self._collection = self._client.get_or_create_collection(
name=name,
metadata={"hnsw:space": "cosine"}
)
Протестируем, объединив с нашим Embedder из главы 4.2:
python
# test_vector_store.py
from embedder import Embedder
from vector_store import VectorStore
# 1. Загружаем embedding-модель
emb = Embedder()
# 2. Создаём хранилище
store = VectorStore()
# 3. Добавим несколько тестовых чанков
texts = [
"Гарантийный срок на оборудование составляет 12 месяцев с даты поставки.",
"Стороны обязаны уведомлять друг друга об изменении реквизитов.",
"В случае просрочки оплаты начисляется пеня 0.1% за каждый день просрочки.",
"Поставщик гарантирует устранение недостатков в течение 30 календарных дней."
]
embeddings = emb.embed_documents(texts)
store.add_documents(
ids=[f"doc_{i}" for i in range(len(texts))],
embeddings=embeddings,
documents=texts,
metadatas=[{"source": "test.txt", "chunk": i} for i in range(len(texts))]
)
print(f"Документов в коллекции: {store.count()}")
# 4. Ищем по вопросу
query = "Какие гарантийные обязательства у поставщика?"
query_vec = emb.embed_query(query)
results = store.search(query_vec, n_results=2)
print("\nРезультаты поиска:")
for i, (doc, meta, dist) in enumerate(zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0]
)):
print(f"{i+1}. [distance: {dist:.3f}] {doc}")
print(f" Источник: {meta['source']}\n")
Запустим:
bash
python test_vector_store.py
Вывод:
text
Загрузка embedding-модели intfloat/multilingual-e5-base...
Модель загружена, размерность векторов: 768
Документов в коллекции: 4
Результаты поиска:
1. [distance: 0.123] Поставщик гарантирует устранение недостатков в течение 30 календарных дней.
Источник: test.txt
2. [distance: 0.145] Гарантийный срок на оборудование составляет 12 месяцев с даты поставки.
Источник: test.txt
Видно, что поиск нашёл релевантные чанки о гарантиях, а не о реквизитах или пене. Расстояние (distance) для косинусной метрики — чем меньше, тем ближе по смыслу.
Что дальше
У нас есть все кирпичики для RAG: embedding-модель и векторная база данных. В следующей главе мы напишем функцию, которая обходит папку с документами, читает файлы PDF, DOCX, TXT, разбивает их на чанки и индексирует в ChromaDB. Это будет глава 4.4 «Практикум: индексация папки с документами».












