RAG для документации Python: гайд по локальному ИИ ассистенту | AiManual
AiManual Logo Ai / Manual.
25 Май 2026 Гайд

RAG для документации разработчика: как научить локальную LLM понимать новые библиотеки Python

Пошаговое руководство: как поднять локальный RAG пайплайн с Ollama, ChromaDB и эмбеддингами, чтобы LLM знала последние версии pandas, fastapi, httpx.

Ваша любимая LLM — гениальный дилетант. Она перечитала пол-интернета, но застыла во времени. Спросите у llama3.1 про pandas 2.2 — она с умным видом выдаст API двухлетней давности, а если надавить — начнет галлюцинировать несуществующими параметрами. Знакомо? Я тоже через это прошел.

Проблема в том, что веса LLM фиксированы после обучения. Новые фичи fastapi, httpx или sqlalchemy 2.0 в них не просочились. И переучивать модель каждый раз — разорительно (и бессмысленно). Но есть лазейка: RAG (Retrieval-Augmented Generation). Вместо того чтобы нашпиговывать знаниями саму модель, мы подкладываем ей шпаргалку из актуальной документации прямо в момент вопроса.

Итог: вы получаете локального ассистента, который точно знает, как выглядит сигнатура pd.DataFrame.map в 2026 году, и не выдумывает чушь. Вся магия — в 50 строках Python, Ollama и одной векторной базе.

Почему просто скормить документацию в промпт — плохая идея

Технически вы можете запихнуть всю доку requests в промпт. Но у локальных LLM контекстное окно обычно 8-32K токенов, а документация — сотни тысяч. Результат: либо обрежете, либо модель забудет начало. RAG решает это элегантно — держим в памяти только то, что нужно в текущем диалоге, а база знаний лежит рядом в индексе.

Если вы ещё не знакомы с основами RAG, советую сначала пробежаться по полному руководству по RAG — там разобрана архитектура и типовые грабли. А для быстрого старта есть гайд по RAG за 15 минут.

Инструменты на май 2026: что ставим

Компонент Что используем Почему он
Локальная LLM deepseek-coder-33b (через Ollama) Отличное знание Python, 33B параметров — золотая середина для одной RTX 4090
Модель эмбеддингов nomic-embed-text:v1.5 (Ollama) 768-мерные векторы, хорошо понимает код и текст
Векторная БД ChromaDB 0.6.3 Встраивается в Python-скрипт, не требует отдельного сервера
Чанкер RecursiveCharacterTextSplitter из langchain-text-splitters 0.4 Умно режет по естественным границам (заголовки, пустые строки)

Предупреждение: Если у вас нет мощной видеокарты, можно использовать меньшие модели (например, codestral-22b через API или арендовать GPU на Vast.ai). Но RAG-пайплайн сам по себе легкий — эмбеддинги считаются на CPU или слабой GPU.

Как НЕ надо делать: лобовой напильник

Допустим, вы скачали всю документацию pandas в один гигантский Markdown-файл и скормили его в промпт. Что будет? Модель либо ничего не ответит из-за переполнения контекста, либо начнёт выдумывать. RAG потому и Retrieval — мы НЕ даём всё, мы достаём только то, что релевантно вопросу.

Ещё одно распространённое заблуждение: «обучу LoRA на документации». Не надо. LoRA — для смены стиля или добавления новых фактов в веса. RAG дешевле, быстрее и не требует пересборки модели. Только когда вы упрётесь в скорость поиска и не сможете ужать знания в разумные чанки — задумайтесь о fine-tune. А это бывает редко.

Пошаговый план: от документа до работающего чата

1 Собираем документацию

Нам нужны тексты — желательно в Markdown. Большинство проектов на ReadTheDocs отдают HTML. Используем html2text, чтобы конвертировать. Для примера возьмём httpx 0.28 — новейшую версию на май 2026.

import requests
from html2text import HTML2Text

# Скачиваем страницу документации
url = "https://www.python-httpx.org/advanced/"
resp = requests.get(url)
resp.encoding = 'utf-8'

# Конвертируем в Markdown
converter = HTML2Text()
converter.ignore_links = False
markdown = converter.handle(resp.text)

# Сохраняем
with open("httpx_advanced.md", "w", encoding="utf-8") as f:
    f.write(markdown)

Повторите для всех нужных разделов. Или, если документация лежит в репозитории как .rst/.md, клонируйте и используйте прямо их — это даже лучше.

2 Режем на чанки (с умом)

Просто резать по 1000 символов — варварство. Вы порубите пополам описание функции или пример кода. Используем рекурсивный сплиттер с пониманием структуры: он делит по двум переносам строки, потом по заголовкам, потом по предложениям.

from langchain_text_splitters import RecursiveCharacterTextSplitter

with open("httpx_advanced.md", "r", encoding="utf-8") as f:
    text = f.read()

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", " ", ""],
)

chunks = splitter.split_text(text)
print(f"Получилось {len(chunks)} чанков")
💡
Размер чанка и перекрытие — гиперпараметры. Я выбрал 1000/200, потому что эмбеддинги nomic-embed-text хорошо работают с такими длинами. Если документы содержат много кода, увеличьте до 1500.

3 Создаём эмбеддинги и загружаем в ChromaDB

Теперь превращаем чанки в векторы. Ollama поддерживает эмбеддинги из коробки — достаточно указать модель. ChromaDB хранит всё в локальной папке, можно переиспользовать между сессиями.

import chromadb
from ollama import Embed

# Инициализируем Chroma (persistent — сохраняет на диск)
client = chromadb.PersistentClient(path="./chroma_httpx")
collection = client.create_collection(
    name="httpx_advanced",
    metadata={"hnsw:space": "cosine"}  # косинусная близость
)

# Эмбеддинги через Ollama
embeddings = []
for chunk in chunks:
    response = Embed(model="nomic-embed-text:v1.5", input=chunk)
    embeddings.append(response["embedding"])

# Загружаем в коллекцию
collection.add(
    ids=[f"chunk_{i}" for i in range(len(chunks))],
    embeddings=embeddings,
    documents=chunks,
)
print("Индекс создан")

4 Поиск и формирование промпта

Прилетает вопрос пользователя. Мы ищем в индексе 3-5 самых похожих чанков, склеиваем их в контекст и передаём LLM. Вот функция поиска:

def search_and_answer(query: str, n_results: int = 3):
    # Эмбеддинг запроса
    q_emb = Embed(model="nomic-embed-text:v1.5", input=query)["embedding"]
    
    # Поиск в Chroma
    results = collection.query(
        query_embeddings=[q_emb],
        n_results=n_results
    )
    
    retrieved_docs = results["documents"][0]
    context = "\n\n---\n\n".join(retrieved_docs)
    
    # Составляем промпт
    prompt = f"""Ты — ассистент по Python библиотеке httpx. 
Используй только информацию из контекста ниже. 
Если ответа нет, скажи, что не знаешь.

Контекст:
{context}

Вопрос: {query}

Ответ:"""
    
    # Отправляем в LLM (Ollama)
    response = ollama.chat(
        model="deepseek-coder:33b",
        messages=[{"role": "user", "content": prompt}]
    )
    return response["message"]["content"]

# Пример
print(search_and_answer("Как настроить таймауты для всего клиента?"))

5 Обёртка для диалога

Добавим простой цикл ввода-вывода, чтобы не ходить в терминал снова.

while True:
    q = input("\nВаш вопрос (или 'exit'): ")
    if q.lower() == 'exit':
        break
    ans = search_and_answer(q)
    print(f"\nОтвет:\n{ans}\n")

Нюансы, которые сожгут вам час (я уже сжёг)

  • Версионирование документации: если вы проиндексировали requests 2.31, а спрашиваете про 2.32 — модель выдаст по сути то же самое, но новая сигнатура не попадёт. Решение: в метаданных чанка хранить версию и при поиске фильтровать по версии.
  • Контекстное окно: 3-5 чанков по 1000 токенов = 3000-5000 токенов. Плюс диалог — может выйти за 8K. Если модель часто «забывает» чат, либо используйте модель с большим окном (32K+), либо сокращайте количество чанков.
  • Качество эмбеддингов: nomic-embed-text хорош, но для кода лучше bge-m3 (он поддерживает многократность языков). Поменять модель легко — просто пересчитайте эмбеддинги и пересоздайте коллекцию.
  • Фильтрация мусора: в документации бывают навигационные меню, колонтитулы. Их надо вырезать. Или используйте уже очищенные дампы с ReadTheDocs JSON API.
  • Параметр «temperature»: для кода лучше ставить 0.1-0.3, чтобы модель не импровизировала с API.

Когда RAG паллиатив, а когда — серебряная пуля

RAG идеален для конкретных фактов: «какой параметр у pd.read_csv для обработки пропусков?». Если вы хотите, чтобы модель сама разбиралась в концептуальных вопросах (например, «спроектируй систему очередей») — RAG просто даст больше релевантных примеров, но не заменит архитектурное мышление. И это нормально.

Для полного погружения в локальные альтернативы взгляните на статью про NotebookLM на минималках — там похожий принцип, но для документов. А если захотите ускорить поиск по миллионам страниц, добро пожаловать в мир LLMSearchIndex.

Главный совет напоследок: не пытайтесь впихнуть невпихуемое. RAG не заменит память модели — он даёт ей шпаргалку. Делайте чанки осмысленными, фильтруйте лишнее, и ваш локальный ассистент станет лучшим другом, а не диванным экспертом.

Подписаться на канал