RAG (Retrieval-Augmented Generation) — это архитектурный паттерн, при котором языковая модель отвечает на вопросы, используя контекст из внешней базы знаний. Вместо того чтобы полагаться только на обучающие данные, модель получает релевантные документы и формирует ответ на их основе. Это решает проблемы галлюцинаций и устаревших данных. В этой статье построим полноценную RAG-систему с ModelSwitch.
Архитектура RAG-системы
RAG состоит из двух этапов:
- Retrieval (поиск) — находим релевантные фрагменты документов по запросу пользователя через векторный поиск
- Generation (генерация) — передаём найденные фрагменты как контекст в языковую модель, которая формирует ответ
Компоненты системы:
- Embeddings API — преобразование текста в векторы (ModelSwitch)
- Векторная база данных — хранение и поиск векторов (ChromaDB, Pinecone, Qdrant)
- LLM API — генерация ответа на основе контекста (ModelSwitch)
Шаг 1: Индексация документов
Сначала разобьём документы на фрагменты (chunks) и создадим для них эмбеддинги:
from openai import OpenAI
import chromadb
client = OpenAI(
base_url="https://api.modelswitch.ru/v1",
api_key="msk_ваш_ключ"
)
# Инициализация ChromaDB
chroma = chromadb.PersistentClient(path="./chroma_db")
collection = chroma.get_or_create_collection(
name="knowledge_base",
metadata={"hnsw:space": "cosine"}
)
def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
"""Разбиение текста на фрагменты с перекрытием."""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start = end - overlap
return chunks
def index_document(doc_id: str, text: str):
"""Индексация одного документа."""
chunks = chunk_text(text)
# Получаем эмбеддинги пакетом
response = client.embeddings.create(
model="text-embedding-3-small",
input=chunks
)
embeddings = [item.embedding for item in response.data]
# Сохраняем в ChromaDB
collection.add(
ids=[f"{doc_id}_chunk_{i}" for i in range(len(chunks))],
embeddings=embeddings,
documents=chunks,
metadatas=[{"doc_id": doc_id, "chunk_index": i} for i in range(len(chunks))]
)
print(f"Проиндексировано {len(chunks)} фрагментов для документа {doc_id}")
# Индексируем документы
index_document("faq", "ModelSwitch — это AI API Gateway, который предоставляет доступ к 300+ моделям...")
index_document("pricing", "Стоимость GPT-4o: $2.50 за 1M входных токенов, $10.00 за 1M выходных...")
Шаг 2: Поиск релевантного контекста
При получении вопроса пользователя находим наиболее релевантные фрагменты:
def search_context(query: str, top_k: int = 3) -> list[str]:
"""Поиск релевантных фрагментов по запросу."""
# Получаем эмбеддинг запроса
response = client.embeddings.create(
model="text-embedding-3-small",
input=query
)
query_embedding = response.data[0].embedding
# Ищем ближайшие фрагменты в ChromaDB
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
return results["documents"][0]
# Тест поиска
context_chunks = search_context("Сколько стоит GPT-4o?")
for i, chunk in enumerate(context_chunks):
print(f"Фрагмент {i + 1}: {chunk[:100]}...")
Шаг 3: Генерация ответа с контекстом
Передаём найденные фрагменты в промпт и генерируем ответ:
def ask_rag(question: str) -> str:
"""Полный RAG-пайплайн: поиск контекста -> генерация ответа."""
# 1. Находим релевантный контекст
context_chunks = search_context(question, top_k=3)
context = "\n\n".join(context_chunks)
# 2. Формируем промпт с контекстом
system_prompt = (
"Ты полезный ассистент. Отвечай на вопросы ТОЛЬКО на основе "
"предоставленного контекста. Если ответа нет в контексте, "
"скажи 'Информация не найдена в базе знаний'.\n\n"
f"Контекст:\n{context}"
)
# 3. Генерируем ответ
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": question}
],
temperature=0.3 # Низкая температура для фактологических ответов
)
return response.choices[0].message.content
# Тест RAG-системы
answer = ask_rag("Сколько стоит GPT-4o через ModelSwitch?")
print(answer)
Оптимизация RAG-системы
Для улучшения качества RAG-системы применяйте следующие техники:
- Размер чанков — экспериментируйте с chunk_size (300-1000 символов). Маленькие чанки точнее, но теряют контекст; большие — наоборот.
- Перекрытие (overlap) — 10-20% overlap между чанками предотвращает разрыв смысла на границе.
- Re-ranking — после векторного поиска переранжируйте результаты с помощью кросс-энкодера или LLM.
- Гибридный поиск — комбинируйте векторный поиск с BM25 (ключевые слова) для лучших результатов.
- Metadata-фильтры — фильтруйте результаты по дате, категории, источнику для повышения релевантности.
- Оценка качества — измеряйте faithfulness (ответ основан на контексте) и relevance (контекст релевантен вопросу).
RAG через ModelSwitch даёт гибкость в выборе моделей: text-embedding-3-small для эмбеддингов, gpt-4o для генерации, или более дешёвая gpt-4o-mini для массовых запросов.