LangChain RAG: рабочий пример с моделью через шлюз
Строим простой RAG на LangChain: документы, чанки, векторный поиск и ответ модели через шлюз. Считаем, из чего складывается цена вопроса, и режем лишние токены.
5 мин · · разработка · экономия
LangChain RAG на Python решает типичную задачу: у вас есть свои документы, а модель о них не знает. Вместо того чтобы вставлять в запрос всё содержимое базы, мы находим несколько релевантных фрагментов и отправляем только их. Ниже собираем минимальный рабочий пример: загрузка, разбиение, индекс, поиск и ответ модели через OpenAI-совместимый шлюз. Заодно считаем, где тратятся токены. Код следует документации LangChain (разделы Retrieval и семантический поиск); версии пакетов сверяйте с ней.
LangChain RAG: из чего он состоит
Документация LangChain делит индексацию на четыре шага: загрузить данные в объекты Document, разбить на фрагменты, превратить фрагменты в векторы (embeddings) и сохранить в векторное хранилище. При запросе вопрос тоже превращается в вектор, ближайшие фрагменты отправляются модели как контекст. Разбиение нужно по двум причинам: большие куски хуже ищутся и тратят лишние токены.
Одно ограничение важно сразу. В справочнике API Zukko нет эндпоинта эмбеддингов (есть чат, Messages, Responses, список моделей и другие). Поэтому векторизацию мы делаем локально, а облачную модель через шлюз используем только для ответа. Так вы и платите шлюзу только за вопросы, а не за индексацию.
Установка
pip install langchain langchain-openai langchain-text-splitters langchain-huggingface sentence-transformers
export ZUKKO_API_KEY="fetch_ваш_ключ"Положите несколько текстовых или Markdown-файлов в папку knowledge/.
Индекс: загрузка, разбиение, векторы
from pathlib import Path
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
docs = [
Document(page_content=p.read_text(encoding="utf-8"), metadata={"source": p.name})
for p in Path("knowledge").glob("*.md")
]
splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
chunks = splitter.split_documents(docs)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
store = InMemoryVectorStore(embedding=embeddings)
store.add_documents(chunks)
retriever = store.as_retriever(search_kwargs={"k": 4})Что здесь важно:
RecursiveCharacterTextSplitterпо документации LangChain считается разумным выбором по умолчанию: он старается сохранить абзацы, потом предложения.chunk_sizeздесь в символах,chunk_overlapэто перекрытие соседних фрагментов. Размер фрагмента подбирайте на своих вопросах: слишком мелкие фрагменты теряют контекст, слишком крупные тратят токены.- Модель эмбеддингов для русского текста должна быть мультиязычной. Мы взяли мультиязычную модель из семейства sentence-transformers как пример; для своих текстов сравните несколько.
InMemoryVectorStoreхранит всё в памяти и подходит для примера. Для постоянной базы выберите хранилище из списка интеграций LangChain.- Если не хочется скачивать модель эмбеддингов, есть вариант полегче:
BM25Retriever.from_documents(chunks, k=4)изlangchain_community.retrievers(нужен пакетrank_bm25). Он ищет по словам без учёта морфологии, поэтому для русского текста пропускает формы слов, и качество проверяйте на своих вопросах.
Ответ модели через шлюз
Облачная модель подключается как ChatOpenAI с адресом шлюза. База для OpenAI-клиентов заканчивается на /v1:
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-6.1-sol",
base_url="https://api.zukko.pro/v1",
api_key=os.environ["ZUKKO_API_KEY"],
max_tokens=600,
)
SYSTEM = (
"Отвечай только по приведённому контексту. "
"Если ответа в контексте нет, скажи «не знаю». "
"В конце укажи номера источников в квадратных скобках."Запрос «отвечай только по контексту» и разрешение сказать «не знаю» снижают риск выдуманных ответов, но не убирают его: проверяйте на вопросах, ответа на которые в базе нет. Подробный разбор схемы без лишних компонентов есть в статье RAG-пайплайн своими руками. Подключение шлюза к LangChain описано здесь: LangChain и base_url.
Где тратятся токены
За токены платите только на последнем шаге. Один вопрос состоит из:
- системной инструкции (десятки токенов);
- найденного контекста:
kфрагментов поchunk_sizeсимволов, обычно самая тяжёлая часть; - самого вопроса;
- ответа модели (ограничен
max_tokens).
Пусть на вопрос уходит 1 800 входных токенов (контекст около 1 500, остальное инструкция и вопрос; точное число измерьте на своих текстах) и 300 выходных. Формула: вход × цена входа / 1M + выход × цена выхода / 1M. Цены на 18 октября 2026:
| Модель | Вход / выход, $ за 1M | Расчёт, миллионные доли $ | Один вопрос | 10 000 вопросов |
|---|---|---|---|---|
gpt-6.1-sol | 0.27 / 1.32 | 1800 × 0.27 + 300 × 1.32 = 486 + 396 | $0.000882 | $8.82 |
claude-sonnet-5-5 | 0.4 / 2 | 720 + 600 | $0.00132 | $13.20 |
deepseek-v4.1-flash | 0.013 / 0.038 | 23.4 + 11.4 | $0.0000348 | $0.348 |
Для сравнения вспомним подход «положить весь документ в промпт». При документе в 200 000 токенов на gpt-6.1-sol один вопрос обойдётся в 200 000 × 0.27 / 1M = $0.054, а 10 000 вопросов в $540. RAG оправдан именно тогда, когда документов много или вопросов много; для разовой работы с одним файлом длинный контекст может быть проще. Для Claude в OpenAI-формате весь вход идёт по цене входа.
Как сократить расход
- Уменьшите
kиchunk_size. Каждый лишний фрагмент оплачивается при каждом вопросе. Начните сk=3и проверьте качество на 20–30 реальных вопросах. - Уберите дубли. Из-за перекрытия соседние фрагменты повторяют текст; не вставляйте подряд оба, если они о том же.
- Ограничьте ответ.
max_tokensи просьба отвечать кратко режут выходные токены, которые стоят дороже входных. - Берите дешёвую модель для простых вопросов. Если контекст уже содержит ответ, часто хватает небольшой модели. Проверьте на своих данных, а схему «сначала дешёвая, сложное эскалируем» описывает статья про каскад моделей.
- Не тащите историю целиком. В чате передавайте последние реплики, а не весь диалог.
- Кэш. Если у вас длинный повторяющийся префикс (инструкции, примеры), то у GPT-моделей шлюз видит чтение кэша в обоих форматах, и эта часть оплачивается по цене чтения. Для Claude скидку за кэш даёт только Anthropic-формат. Подробнее: кэш и лимиты ключа.
Частые вопросы
Нужен ли для RAG отдельный сервис эмбеддингов?
Шлюз Zukko эндпоинта эмбеддингов не предоставляет, поэтому в примере они считаются локально. Если у вас уже есть сервис эмбеддингов, подключите его через соответствующий класс LangChain.
Какую модель взять для ответа?
Ориентируйтесь на цену, контекст и индекс силы: у gpt-6.1-sol он 52, у claude-sonnet-5-5 48, у deepseek-v4.1-flash 39. Хватит ли дешёвой модели, покажет проверка на ваших вопросах.
Как понять, что поиск работает?
Печатайте найденные фрагменты рядом с ответом и проверяйте, лежит ли в них ответ. Если нет, дело в разбиении или эмбеддингах, а не в модели.
Можно ли заменить InMemoryVectorStore?
Да, as_retriever есть у любого векторного хранилища LangChain, остальной код не меняется.
Создайте ключ в кабинете, поставьте ему дневной лимит и запустите пример.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Память чат-бота: как хранить историю диалога и не платить за неё дваждыМодель не помнит предыдущих реплик: историю отправляете вы при каждом запросе. Разбираем, как растёт цена диалога и чем её сдерживать.
- LangChain text splitters: как резать текст на чанки правильноКак подобрать размер чанка и перекрытие в RecursiveCharacterTextSplitter, почему считать нужно в токенах и как сверить подсчёт с точным количеством от шлюза.
- Агенты opencode: как настроить роли, модели и инструментыСвой агент в opencode — это роль, модель и список разрешений. Показываем конфиг и пример, где рутину делает дешёвая модель, а код правит сильная.
- Plan mode в Claude Code: сначала план, потом правки в кодеВ plan mode Claude Code читает код и предлагает план, но ничего не правит, пока вы не согласитесь. Как включить, использовать и во сколько это обходится.