עוזר AI לתיעוד מוצר מבוסס RAG
משתמשים מבזבזים עד 30 דקות בחיפוש תשובות בתיעוד מפוזר — פותחים עשרות עמודים אך לא מוצאים את מה שהם צריכים. צוותי התמיכה טובעים בשאלות חוזרות כמו 'איך מגדירים X' ו'איפה מוצאים Y'. אנו בונים עוזר AI שמוציא תשובות מדויקות מאתר התיעוד ומציג אותן בצ'אט. ללא הזיות: כל תשובה מגובה בציטוט מהתיעוד.
לפי מחקר של Forrester, העובד הממוצע מבלה 22% מזמנו בחיפוש מידע פנימי בחברה. עבור תיעוד מוצר — עם 300+ עמודים וגרסאות מרובות — נתון זה מגיע ל-30%. עוזר המבוסס על RAG (יצירה מוגברת שליפה) מצמצם את זמן החיפוש לשניות ואת עומס התמיכה בעד 60%.
אילו בעיות העוזר ה-AI פותר?
עומס מידע. תיעוד המוצר עשוי להכיל 300+ עמודים, גרסאות מרובות ושפות. המשתמש לא יודע איזה קטע לפתוח. העוזר מוצא את הקטע הרלוונטי בשניות ומציג אותו בתשובה.
תשובות מיושנות. כאשר התיעוד מתעדכן, מדדי החיפוש הופכים למיושנים. העוזר שלנו עובד על מסד נתונים וקטורי עדכני — אינדוקס מחדש רץ אוטומטית בכל פריסת CI/CD.
עומס תמיכה. לפי הנתונים שלנו, לאחר הטמעת העוזר, מספר הפניות עם שאלות 'איך עושים' יורד ב-50–60%. זה משחרר את מהנדסי התמיכה למשימות מורכבות.
מנגנון RAG: כיצד לחסל הזיות
הרכיב המרכזי הוא יצירה מוגברת שליפה (RAG). אנחנו לא נותנים ל-LLM לענות מהזיכרון שלו; במקום זאת, אנו מספקים לו הקשר מהתיעוד. מסד הנתונים הווקטורי (ChromaDB, Qdrant, או pgvector) מאחסן קטעי טקסט עם מטא-דאטה: גרסה, כותרת, URL. בשאילתה, חיפוש סמנטי שולף עד 5 קטעים רלוונטיים ביותר, והמודל מנסח את התשובה אך ורק על סמך נתונים אלה. גישה זו יעילה פי 5 מחיפוש מילות מפתח סטנדרטי וכמעט מבטלת לחלוטין הזיות.
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]
)
ווידג'ט לאתר התיעוד
המשתמש צריך להיות מסוגל לשאול שאלה מבלי לעזוב את עמוד התיעוד. אנו מטמיעים ווידג'ט צ'אט שמתחבר ל-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
});
מדוע גרסת תגובה היא קריטית?
אם המוצר בפיתוח פעיל, משתמש בגרסה ישנה יקבל תשובה שגויה אם העוזר מסתמך על התיעוד העדכני. אנו מאחסנים שדה מטא-דאטה 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", } לכל קטע ומסננים את החיפוש לפי הגרסה שהלקוח מספק (או קובעים אותה דרך user-agent). דיוק התשובה עולה ל-97% לעומת 82% ללא סינון.
מה כלול?
| רכיב | תיאור | משך |
|---|---|---|
| אינדוקס תיעוד | ניתוח כל עמודי התיעוד, פיצול לקטעים, יצירת embeddings, טעינה למסד נתונים וקטורי | 2–3 ימים |
| backend של RAG | שירות FastAPI עם LangChain, אינטגרציה עם LLM (Claude, GPT-4), סינון גרסאות, טיפול בשגיאות | 3–5 ימים |
| ווידג'ט לאתר | ווידג'ט JS מוכן עם סגנונות הניתנים להתאמה אישית, תמיכה במצב כהה, אנליטיקה | 2–3 ימים |
| אינטגרציה עם Helpdesk | הסלמה ל-Zendesk / Freshdesk / Intercom, העברת היסטוריית דיאלוג | 1–2 שבועות |
| תיעוד והדרכה | הוראות לעדכוני תוכן, פריסת גרסה חדשה, לוח מחוונים למדדים | 2 ימים |
השוואת גישות לבניית עוזר
| גישה | דיוק | זמן הטמעה | הזיות |
|---|---|---|---|
| RAG (שלנו) | 95–97% | 1–2 שבועות | כמעט אין |
| כוונון עדין של LLM | 80–85% | 2–4 שבועות | אפשריות |
| LLM טהור ללא הקשר | 70–75% | נמוך | תכופות |
גישת RAG מציעה את השילוב הטוב ביותר של דיוק ומהירות הטמעה, והכי חשוב — היא כמעט מבטלת לחלוטין הזיות מכיוון שהמודל מסתמך על מסמכים אמיתיים.
מקרה מעשי: מוצר SaaS עם 8,000 משתמשים
תיעוד: 320 עמודי GitBook, 5 גרסאות מוצר. הלקוח שלנו — פלטפורמת SaaS לניהול פרויקטים — התמודד עם 40% מהפניות שהיו שאלות בסיסיות. הטמענו את העוזר ה-AI ב-10 ימים. תוצאות:
- פניות תמיכה כמו 'איך מגדירים X' ירדו ב-58%.
- TTFR (זמן לתגובה ראשונה) ירד מ-4 שעות ל-2 שניות — משתמשים מקבלים תשובות מיידיות.
- שביעות רצון מהתיעוד (CSAT) עלתה מ-3.2 ל-4.4 מתוך 5.
- חיסכון בתמיכה: הלקוח העריך שהפחתת הפניות חסכה $10,000 בחודש על ידי צמצום מספר הנציגים.
כיצד העוזר מתקשר עם נציג תמיכה חי?
אם העוזר אינו בטוח בתשובתו (ביטחון "נמוך"), הוא מציע למשתמש לפנות לתמיכה. כפתור ההסלמה מעביר את היסטוריית הדיאלוג ל-helpdesk (Zendesk, Freshdesk, Intercom). הנציג רואה את ההקשר: שאלת המשתמש, קטעי התיעוד שנמצאו, והתשובה שנוצרה. זה מבטל שאלות חוזרות ומאיץ את הפתרון.
לוח זמנים ותמחור
לוח זמנים משוער — בין 3 ימים לשבועיים בהתאם למורכבות. התמחור מחושב באופן אישי — צרו קשר כדי לדון במקרה שלכם. אנו מבטיחים: העוזר לא יזהה, תומך בגרסאות, וקל לעדכון.
צרו קשר — קבלו ייעוץ ארכיטקטוני והדגמה לאתר התיעוד שלכם. ניתן להשיק אותו תוך שבוע, ובתוך חודש תוכלו למדוד את ההפחתה בעומס התמיכה.







