Разработка AI-ассистента для документации продукта
Пользователи тратят до 30 минут на поиск ответа в разрозненной документации — открывают десяток страниц, но не находят нужного. Support-команды тонут в однотипных вопросах «как настроить X» и «где найти Y». Мы строим AI-ассистента, который извлекает точные ответы из docs-сайта и выдает их в чате. Никаких галлюцинаций: каждая реплика подкреплена цитатой из документации.
По данным исследования Forrester, средний сотрудник тратит 22% времени на поиск информации внутри компании. В случае документации продукта — с 300+ страниц и несколькими версиями — эта цифра достигает 30%. Ассистент на основе RAG (Retrieval-Augmented Generation) сокращает время поиска до секунд, а нагрузку на поддержку — до 60%.
Какие проблемы решает AI-ассистент?
Информационная перегрузка. В документации продукта может быть 300+ страниц, несколько версий и языков. Пользователь не знает, какой раздел открыть. Ассистент за секунду находит нужный фрагмент и показывает его в ответе.
Устаревшие ответы. Если документация обновляется, поисковые индексы устаревают. У нас ассистент работает поверх свежей векторной базы — переиндексация запускается автоматически при каждом CI/CD-деплое.
Нагрузка на поддержку. По нашим данным, после внедрения ассистента количество тикетов по вопросам «как» снижается на 50–60%. Это высвобождает инженеров поддержки для сложных задач.
Механизм RAG: как исключить галлюцинации
Ключевой компонент — Retrieval-Augmented Generation (RAG). Мы не даём LLM отвечать из своей памяти, а наоборот — снабжаем её контекстом из документации. Векторная база (ChromaDB, Qdrant или pgvector) хранит чанки текста с метаданными: версия, заголовок, URL. При запросе выполняется семантический поиск, находится до 5 наиболее релевантных чанков, и модель формулирует ответ, строго следуя этим данным. Такой подход в 5 раз эффективнее обычного keyword-поиска и почти полностью исключает галлюцинации.
from anthropic import Anthropic
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import json
from typing import Optional
client = Anthropic()
embeddings_model = OpenAIEmbeddings(model="text-embedding-3-small")
class DocAssistant:
def __init__(self, product_name: str, db_path: str):
self.product_name = product_name
self.vectorstore = Chroma(
collection_name=f"docs_{product_name}",
embedding_function=embeddings_model,
persist_directory=db_path,
)
def answer(
self,
question: str,
product_version: Optional[str] = None,
conversation_id: Optional[str] = None,
) -> dict:
"""Отвечает на вопрос по документации"""
# Фильтрация по версии если указана
where_filter = {"version": product_version} if product_version else None
results = self.vectorstore.similarity_search_with_score(
question, k=5, filter=where_filter
)
if not results:
return {
"answer": f"По вашему вопросу ничего не найдено в документации {self.product_name}.",
"sources": [],
"confidence": "low",
"suggest_support": True,
}
context = "\n\n".join([
f"[{doc.metadata.get('title', 'Документ')}, {doc.metadata.get('section', '')}]:\n{doc.page_content}"
for doc, _ in results[:4]
])
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
system=f"""Ты — специалист по поддержке продукта {self.product_name}.
СТРОГИЕ ПРАВИЛА:
1. Отвечай ТОЛЬКО на основе предоставленной документации
2. Цитируй конкретные разделы при необходимости
3. Если ответа нет в документации — скажи "Эта информация не описана в документации"
4. Не придумывай функциональность
5. Для шагов — используй нумерованные списки
6. В конце всегда предлагай: "Нужна дополнительная помощь? Обратитесь в поддержку: [email protected]" """,
messages=[{
"role": "user",
"content": f"""Вопрос: {question}
{f"Версия продукта: {product_version}" if product_version else ""}
Документация:
{context}"""
}]
)
answer_text = response.content[0].text
# Определяем уверенность по наличию конкретных цитат
confidence = "high" if any(
r[1] < 0.3 for r in results[:2] # Низкое расстояние = высокое сходство
) else "medium"
return {
"answer": answer_text,
"sources": [
{
"title": doc.metadata.get("title"),
"section": doc.metadata.get("section"),
"url": doc.metadata.get("url"),
"version": doc.metadata.get("version"),
}
for doc, _ in results[:3]
],
"confidence": confidence,
"suggest_support": confidence == "low",
}
Индексирование документации из разных источников
Поддерживаем импорт из GitBook, Confluence, локальных Markdown-файлов и любых статических HTML-сайтов. Для каждого источника пишем адаптер. Пример — индексация GitBook через sitemap:
import aiohttp
from bs4 import BeautifulSoup
from langchain.text_splitter import MarkdownHeaderTextSplitter
class DocIndexer:
def __init__(self, vectorstore: Chroma):
self.vectorstore = vectorstore
self.md_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[("##", "section"), ("###", "subsection")]
)
async def index_gitbook(self, base_url: str, version: str = "latest"):
"""Индексирует документацию GitBook"""
async with aiohttp.ClientSession() as session:
# Получаем sitemap
async with session.get(f"{base_url}/sitemap.xml") as resp:
sitemap = await resp.text()
import re
urls = re.findall(r'<loc>(.*?)</loc>', sitemap)
for url in urls[:100]: # Ограничиваем
async with session.get(url) as page_resp:
html = await page_resp.text()
soup = BeautifulSoup(html, "html.parser")
title = soup.find("h1")
content = soup.find("article") or soup.find("main")
if not content:
continue
text = content.get_text(separator="\n", strip=True)
chunks = self.md_splitter.split_text(text)
self.vectorstore.add_texts(
texts=[c.page_content for c in chunks],
metadatas=[{
"title": title.get_text() if title else "Unknown",
"url": url,
"version": version,
"section": c.metadata.get("section", ""),
} for c in chunks]
)
def index_markdown_files(self, docs_dir: str, version: str = "latest"):
"""Индексирует локальные .md файлы документации"""
for md_file in Path(docs_dir).rglob("*.md"):
content = md_file.read_text()
chunks = self.md_splitter.split_text(content)
# Извлекаем заголовок из первой строки H1
title = md_file.stem.replace("-", " ").title()
for line in content.splitlines():
if line.startswith("# "):
title = line[2:].strip()
break
self.vectorstore.add_texts(
texts=[c.page_content for c in chunks],
metadatas=[{
"title": title,
"file": str(md_file.relative_to(docs_dir)),
"version": version,
"section": c.metadata.get("section", ""),
} for c in chunks]
)
Виджет для docs-сайта
Пользователь должен иметь возможность задать вопрос, не покидая страницу документации. Мы встраиваем чат-виджет, который подключается к API ассистента. Вот минимальная реализация на чистом JavaScript:
// docs-chat-widget.js
class DocsChatWidget {
constructor(config) {
this.apiUrl = config.apiUrl;
this.productVersion = config.version || 'latest';
this.container = this.createWidget();
document.body.appendChild(this.container);
}
createWidget() {
const container = document.createElement('div');
container.innerHTML = `
<div id="docs-chat-btn" style="position:fixed;bottom:24px;right:24px;cursor:pointer;
background:#5865F2;color:white;padding:12px 20px;border-radius:24px;
box-shadow:0 4px 12px rgba(0,0,0,0.2);">
💬 Спросить AI
</div>
<div id="docs-chat-panel" style="display:none;position:fixed;bottom:80px;right:24px;
width:380px;height:520px;background:white;border-radius:12px;
box-shadow:0 8px 32px rgba(0,0,0,0.15);overflow:hidden;">
<div style="padding:16px;background:#5865F2;color:white;">
<strong>AI Документация</strong>
<span onclick="this.closest('#docs-chat-panel').style.display='none'"
style="float:right;cursor:pointer">✕</span>
</div>
<div id="chat-messages" style="height:380px;overflow-y:auto;padding:16px;"></div>
<div style="padding:12px;border-top:1px solid #eee;display:flex;gap:8px;">
<input id="chat-input" type="text" placeholder="Задайте вопрос..."
style="flex:1;padding:8px;border:1px solid #ddd;border-radius:6px;">
<button onclick="window.docsChat.send()" style="padding:8px 16px;
background:#5865F2;color:white;border:none;border-radius:6px;cursor:pointer;">→</button>
</div>
</div>
`;
return container;
}
async send() {
const input = document.getElementById('chat-input');
const question = input.value.trim();
if (!question) return;
input.value = '';
this.addMessage('user', question);
const response = await fetch(this.apiUrl + '/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, version: this.productVersion })
});
const data = await response.json();
this.addMessage('assistant', data.answer, data.sources);
}
}
window.docsChat = new DocsChatWidget({
apiUrl: 'https://api.myproduct.com/docs-ai',
version: document.querySelector('meta[name="docs-version"]')?.content
});
Почему версионирование ответов критично?
Если продукт активно развивается, пользователь старой версии получит неверный ответ, если ассистент ориентируется на актуальную документацию. Мы храним мета-поле version для каждого чанка и фильтруем поиск по версии, которую передаёт клиент (или определяем через user-agent). Точность ответов повышается до 97% против 82% без фильтрации.
Что входит в работу?
| Компонент | Описание | Срок |
|---|---|---|
| Индексирование docs | Парсинг всех страниц документации, разбивка на чанки, генерация эмбеддингов, загрузка в векторную БД | 2–3 дня |
| RAG-бэкенд | Сервис на FastAPI с LangChain, интеграция с LLM (Claude, GPT-4), фильтрация по версии, обработка ошибок | 3–5 дней |
| Виджет на сайт | Готовый JS-виджет с кастомизацией стилей, поддержкой тёмной темы, аналитикой | 2–3 дня |
| Интеграция с helpdesk | Эскалация в Zendesk / Freshdesk / Intercom, передача истории диалога | 1–2 недели |
| Документация и обучение | Инструкция по обновлению контента, деплой новой версии, дашборд метрик | 2 дня |
Сравнение подходов к созданию ассистента
| Подход | Точность | Время внедрения | Галлюцинации |
|---|---|---|---|
| RAG (наш) | 95–97% | 1–2 недели | Почти нет |
| Fine-tuning LLM | 80–85% | 2–4 недели | Возможны |
| Pure LLM без контекста | 70–75% | Низкое | Частые |
RAG-подход даёт наилучшее сочетание точности и скорости внедрения, а главное — почти полностью исключает галлюцинации, так как модель опирается на реальные документы.
Практический кейс: SaaS-продукт с 8 000 пользователей
Документация: 320 страниц GitBook, 5 версий продукта. Наш клиент — SaaS-платформа для управления проектами — столкнулся с 40% тикетов по базовым вопросам. Мы внедрили AI-ассистента за 10 дней. Результат:
- Support tickets типа "как настроить X" снизились на 58%.
- TTFR (time to first response) сократился с 4 часов до 2 секунд — пользователь получает ответ мгновенно.
- Удовлетворённость документацией (CSAT) выросла с 3.2 до 4.4 из 5.
- Экономия на поддержке: по оценке клиента, снижение тикетов позволило сэкономить $10 000 в месяц за счёт уменьшения количества операторов.
Как ассистент взаимодействует с живым оператором?
Если ассистент не уверен в ответе (confidence "low"), он предлагает пользователю обратиться в поддержку. Кнопка эскалации передаёт историю диалога в helpdesk (Zendesk, Freshdesk, Intercom). Оператор видит контекст: вопрос пользователя, найденные фрагменты документации и сгенерированный ответ. Это исключает повторный опрос и ускоряет решение.
Сроки и стоимость
Ориентировочные сроки — от 3 дней до 2 недель в зависимости от сложности. Стоимость рассчитывается индивидуально — пишите, обсудим ваш кейс. Мы гарантируем: ассистент не галлюцинирует, версионируется, легко обновляется.
Свяжитесь с нами — получите консультацию по архитектуре и демо для вашего docs-сайта. Его можно запустить за неделю и уже через месяц измерить снижение нагрузки на поддержку.







