AI Assistant for Product Documentation on RAG
Users spend up to 30 minutes searching for answers in scattered documentation — opening dozens of pages but not finding what they need. Support teams drown in repetitive questions like 'how to configure X' and 'where to find Y'. We build an AI assistant that extracts precise answers from the docs site and delivers them in chat. No hallucinations: every response is backed by a citation from the documentation.
According to a Forrester study, the average employee spends 22% of their time searching for internal company information. For product documentation — with 300+ pages and multiple versions — this figure reaches 30%. An assistant based on RAG (Retrieval-Augmented Generation) reduces search time to seconds and support load by up to 60%.
What Problems Does the AI Assistant Solve?
Information overload. Product documentation may contain 300+ pages, multiple versions, and languages. The user doesn't know which section to open. The assistant finds the relevant fragment in seconds and presents it in the answer.
Outdated answers. When documentation updates, search indices become stale. Our assistant works on a fresh vector database — reindexing runs automatically on every CI/CD deploy.
Support load. Based on our data, after implementing the assistant, the number of tickets with 'how-to' questions drops by 50–60%. This frees up support engineers for complex tasks.
RAG Mechanism: How to Eliminate Hallucinations
The key component is Retrieval-Augmented Generation (RAG). We don't let the LLM answer from its memory; instead, we supply it with context from the documentation. The vector database (ChromaDB, Qdrant, or pgvector) stores text chunks with metadata: version, title, URL. On a query, semantic search retrieves up to 5 most relevant chunks, and the model formulates the answer strictly based on this data. This approach is 5 times more efficient than standard keyword search and almost completely eliminates hallucinations.
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:
"""Answers a question based on documentation"""
# Filter by version if specified
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"No information found in {self.product_name} documentation for your question.",
"sources": [],
"confidence": "low",
"suggest_support": True,
}
context = "\n\n".join([
f"[{doc.metadata.get('title', 'Document')}, {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"""You are a support specialist for {self.product_name}.
STRICT RULES:
1. Answer ONLY based on the provided documentation
2. Cite specific sections when necessary
3. If the answer is not in the documentation, say "This information is not described in the documentation"
4. Do not invent functionality
5. For steps, use numbered lists
6. Always end with: "Need further help? Contact support: [email protected]" """,
messages=[{
"role": "user",
"content": f"""Question: {question}
{f"Product version: {product_version}" if product_version else ""}
Documentation:
{context}"""
}]
)
answer_text = response.content[0].text
# Determine confidence based on specific citations
confidence = "high" if any(
r[1] < 0.3 for r in results[:2] # Low distance = high similarity
) 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",
}
Indexing Documentation from Different Sources
We support import from GitBook, Confluence, local Markdown files, and any static HTML sites. We write an adapter for each source. Example — indexing GitBook via 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"):
"""Index GitBook documentation"""
async with aiohttp.ClientSession() as session:
# Get 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]: # Limit
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"):
"""Index local .md documentation files"""
for md_file in Path(docs_dir).rglob("*.md"):
content = md_file.read_text()
chunks = self.md_splitter.split_text(content)
# Extract title from first H1 line
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]
)
Widget for the Docs Site
The user should be able to ask a question without leaving the documentation page. We embed a chat widget that connects to the assistant's API. Here's a minimal implementation in pure 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);">
💬 Ask 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 Documentation</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="Ask a question..."
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
});
Why Is Response Versioning Critical?
If the product is actively developed, a user on an older version will get an incorrect answer if the assistant relies on the latest documentation. We store a version meta-field for each chunk and filter the search by the version the client provides (or determine it via user-agent). Answer accuracy increases to 97% versus 82% without filtering.
What's Included?
| Component | Description | Duration |
|---|---|---|
| Docs indexing | Parse all documentation pages, split into chunks, generate embeddings, load into vector DB | 2–3 days |
| RAG backend | FastAPI service with LangChain, integration with LLM (Claude, GPT-4), version filtering, error handling | 3–5 days |
| Site widget | Ready JS widget with customizable styles, dark theme support, analytics | 2–3 days |
| Helpdesk integration | Escalation to Zendesk / Freshdesk / Intercom, transfer of dialog history | 1–2 weeks |
| Documentation and training | Instructions for content updates, deploy new version, metrics dashboard | 2 days |
Comparison of Approaches to Building an Assistant
| Approach | Accuracy | Time to Implement | Hallucinations |
|---|---|---|---|
| RAG (ours) | 95–97% | 1–2 weeks | Almost none |
| Fine-tuning LLM | 80–85% | 2–4 weeks | Possible |
| Pure LLM without context | 70–75% | Low | Frequent |
The RAG approach offers the best combination of accuracy and implementation speed, and most importantly, it almost completely eliminates hallucinations because the model relies on real documents.
Practical Case: SaaS Product with 8,000 Users
Documentation: 320 GitBook pages, 5 product versions. Our client — a project management SaaS platform — faced 40% of tickets being basic questions. We implemented the AI assistant in 10 days. Results:
- Support tickets like 'how to configure X' dropped by 58%.
- TTFR (time to first response) decreased from 4 hours to 2 seconds — users get instant answers.
- Documentation satisfaction (CSAT) rose from 3.2 to 4.4 out of 5.
- Support savings: the client estimated that the ticket reduction saved $10,000 per month by reducing the number of operators.
How Does the Assistant Interact with a Live Support Agent?
If the assistant is not confident in its answer (confidence "low"), it suggests the user contact support. The escalation button transfers the dialog history to the helpdesk (Zendesk, Freshdesk, Intercom). The agent sees the context: the user's question, the found documentation fragments, and the generated answer. This eliminates repeated questioning and speeds up resolution.
Timeline and Pricing
Estimated timeline — from 3 days to 2 weeks depending on complexity. Pricing is calculated individually — contact us to discuss your case. We guarantee: the assistant will not hallucinate, supports versioning, and is easy to update.
Contact us — get an architecture consultation and a demo for your docs site. It can be launched in a week, and within a month you can measure the reduction in support load.







