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







