
Коротко о главномРазвернутьСвернуть
- Если попросить языковую модель ответить по свежей документации или конкретному сайту, она с блеском выдумает то, чего там нет.
- Стандартное решение — Retrieval-Augmented Generation (RAG) : сначала достать реальный текст, найти в нём нужные куски и только потом передать их модели в качестве контекста.
- В этой статье соберём работающего RAG-бота, который отвечает на вопросы о любой веб-странице, примерно на 60 строках Python .
Если попросить языковую модель ответить по свежей документации или конкретному сайту, она с блеском выдумает то, чего там нет. Стандартное решение — Retrieval-Augmented Generation (RAG) : сначала достать реальный текст, найти в нём нужные куски и только потом передать их модели в качестве контекста.
В этой статье соберём работающего RAG-бота, который отвечает на вопросы о любой веб-странице, примерно на 60 строках Python . Никаких парсеров HTML, headless-браузеров и сложных фреймворков — только requests , локальная Ollama и чистый Markdown.
Что такое RAG
RAG (retrieval-augmented generation) — это подход, при котором языковая модель не отвечает по своей памяти, а сначала ищет релевантные фрагменты во внешнем тексте, а потом генерирует ответ на их основе. Это резко снижает галлюцинации и позволяет работать с данными, которых не было в обучающей выборке.
Что мы соберём
Архитектура бота простая и универсальная:
- Отправляем URL в сервис извлечения текста и получаем чистый Markdown.
- Разбиваем Markdown на фрагменты (чанки) по границам абзацев.
- Превращаем каждый чанк в вектор — эмбеддинг.
- То же самое делаем с вопросом пользователя.
- Находим чанки, ближайшие к вопросу, по косинусной близости.
- Отдаём найденные фрагменты + вопрос языковой модели с инструкцией отвечать только по контексту.
Такая схема легко масштабируется: можно заменить локальную Ollama на OpenAI, Anthropic или российские модели, а векторы перенести в Qdrant или Chroma.
Что понадобится
- Библиотека requests
- Локально запущенная Ollama с моделями nomic-embed-text и llama3
- Доступ к API извлечения текста (в примере — Web to Markdown/JSON API ; бесплатный тариф даёт 50 запросов в сутки)
Код бота
Сохраните скрипт как rag_bot.py и подставьте свой ключ, если сервис извлечения текста требует авторизации:
import requests
# URL сервиса, который превращает произвольную веб-страницу в Markdown
API_URL = "https://web2md-api-production-d822.up.railway.app/extract"
OLLAMA = "http://localhost:11434" # REST API Ollama
def fetch_markdown(url: str) -> dict:
"""Получить очищенный Markdown по URL."""
r = requests.post(
API_URL,
json={"url": url, "format": "markdown", "max_length": 50000},
# headers={"X-RapidAPI-Key": "ВАШ_КЛЮЧ"}, # если требуется
)
r.raise_for_status()
return r.json()
def chunk_markdown(md: str, max_chars: int = 1200) -> list[str]:
"""Разбить Markdown на чанки по границам абзацев."""
paragraphs = [p.strip() for p in md.split("\n\n") if p.strip()]
chunks, current = [], ""
for p in paragraphs:
if len(current) + len(p) > max_chars and current:
chunks.append(current)
current = p
else:
current = f"{current}\n\n{p}" if current else p
if current:
chunks.append(current)
return chunks
def embed(text: str) -> list[float]:
"""Получить эмбеддинг текста из Ollama."""
r = requests.post(
f"{OLLAMA}/api/embeddings",
json={"model": "nomic-embed-text", "prompt": text},
)
r.raise_for_status()
return r.json()["embedding"]
def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
na = sum(x * x for x in a) ** 0.5
nb = sum(y * y for y in b) ** 0.5
return dot / (na * nb) if na and nb else 0.0
def answer(chunks: list[str], question: str) -> str:
"""Найти релевантные чанки и ответить через LLM."""
q_emb = embed(question)
ranked = sorted(chunks, key=lambda c: cosine(embed(c), q_emb), reverse=True)
context = "\n\n".join(ranked[:3])
prompt = (
"Ответь на вопрос, используя ТОЛЬКО приведённый ниже контекст. "
"Если в контексте нет ответа, так и скажи.\n\n"
f"Контекст:\n{context}\n\n"
f"Вопрос: {question}\nОтвет:"
)
r = requests.post(
f"{OLLAMA}/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False},
)
r.raise_for_status()
return r.json()["response"]
if __name__ == "__main__":
data = fetch_markdown("https://en.wikipedia.org/wiki/Retrieval-augmented_generation")
print(f"Извлечено: {data['title']} ({data['word_count']} слов)")
chunks = chunk_markdown(data["content"])
question = "Как RAG снижает галлюцинации языковых моделей?"
print("Ответ:", answer(chunks, question))На что обратить внимание: Если вы используете RapidAPI-версию сервиса, запрос к API_URL обычно требует заголовка X-RapidAPI-Key . Без ключа бесплатный endpoint может вернуть 401.
Разбор по частям
fetch_markdown — единственный внешний вызов. Сервис сам забирает страницу, убирает навигацию, баннеры и футер и возвращает Markdown: заголовки, абзацы, списки. Это освобождает от зависимостей вроде BeautifulSoup или headless Chrome.
chunk_markdown режет текст на фрагменты примерно по 1200 символов, не разрывая абзацы. Мелкие чанки дают более точный ретривл, но увеличивают число эмбеддингов; для начала 1200 символов — хороший баланс.
embed и cosine превращают текст в векторы и считают их близость. Модель nomic-embed-text из Ollama бесплатна, быстрая и неплохо понимает русский и английский.
answer эмбеддит вопрос, выбирает три самых похожих чанка и строит промпт с жёстким ограничением: отвечать только по контексту. Это главная страховка от галлюцинаций.
Запуск
Установите зависимости и скачайте модели в Ollama:
pip install requests
ollama pull nomic-embed-text
ollama pull llama3
python rag_bot.pyЕсли всё в порядке, в консоли появится примерно такой результат:
Извлечено: Retrieval-augmented generation (1500 слов)
Ответ: RAG снижает галлюцинации, опирая ответы модели на извлечённые документы...Почему важен чистый Markdown
Если скормить эмбеддинг-модели сырой HTML, в векторах окажутся теги <div> , меню навигации и копирайты из футера. В результате поиск похожести выдаст «Copyright © 2026» вместо полезного ответа. Чистый Markdown — заголовки, абзацы, списки — улучшает качество ретривла почти бесплатно.
Если не хочется зависеть от внешнего API, можно заменить fetch_markdown на один из альтернативных вариантов:
- trafilatura — библиотека на Python для извлечения главного текста.
- r.jina.ai/http://URL — бесплатный сервис без ключа.
- Firecrawl — API с поддержкой сканирования сайтов целиком.
- ScrapingBee — прокси + рендеринг для сложных страниц.
Для российских разработчиков локальная Ollama особенно удобна: модели качаются бесплатно, не нужны иностранные карты, а инференс идёт на своём железе.
Куда развивать
- Направьте бота на документацию, чейнджлог или блог конкурента и задавайте вопросы по ним.
- Замените Ollama на OpenAI, Anthropic, Gemini или российские модели — функция answer меняется в двух строках.
- Сохраняйте эмбеддинги в векторную БД: Chroma , Qdrant или FAISS , чтобы индексировать сразу много страниц.
- Используйте формат json вместо Markdown, если нужна структура: параграфы, заголовки, ссылки — отдельно.
RAG — это подход, при котором языковая модель перед ответом ищет релевантные фрагменты во внешнем документе. Это помогает ей отвечать по свежим или специфическим данным, а не по обучающей выборке.
Да. Вместо Web to Markdown/JSON API можно использовать библиотеку trafilatura, сервис r.jina.ai или написать собственный парсер на BeautifulSoup. Главное — получить чистый текст без навигации и рекламы.
Ollama запускает модели локально, бесплатно и без облачных API-ключей. Для учебных и pet-проектов этого достаточно, а при необходимости легко перейти на коммерческий провайдер.
Три очевидных рычага: улучшить очистку текста, подобрать размер чанков и увеличить число релевантных фрагментов в промпте. Также помогает явная инструкция модели отвечать только по контексту.
Подходят любые текстовые страницы: статьи, документация, справочники, блоги. Сложности возникают со страницами, где весь контент загружается JavaScript, или с капчами — там нужен рендеринг и прокси.
Выводы
RAG — не магия, а последовательность простых шагов: получить чистый текст, разрезать его на фрагменты, найти ближайшие к вопросу и отдать их модели. Весь минимальный пайплайн укладывается в короткий Python-скрипт, который можно запустить на своём ноутбуке.
RAG работает ровно так хорошо, каков текст, который вы ему скармливаете. Уберите самую утомительную часть — очистку HTML, — и останется интересное: поиск и генерация. bao001 xiao автор туториала на DEV Community
Исходник идеи — статья «Chat With Any Website: Build a RAG Bot in ~60 Lines of Python» . Попробуйте собрать бота на своей странице и посмотрите, где он справляется, а где начинает фантазировать.


