Розробка 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-сайту. Його можна запустити за тиждень і вже через місяць виміряти зниження навантаження на підтримку.







