Інтеграція AI-асистента для підтримки користувачів
AI-асистент для підтримки — це крок вище звичайного чат-бота. Він знає вашу документацію, базу знань та статуси конкретних користувачів. Коли користувач запитує «чому не працює експорт», асистент шукає відповідь у вашому Help Center, перевіряє тариф користувача та дає конкретну відповідь, а не шаблонну.
RAG архітектура для підтримки
RAG (Retrieval-Augmented Generation) — модель не знає ваш продукт, але при кожному запиті їй дають релевантні куски документації з векторної бази:
Запитання користувача
↓
Векторизація запитання (embedding)
↓
Пошук подібних чанків у Vector DB
↓
LLM отримує: запитання + контекст документації
↓
Відповідь на основі вашого контенту
Стек для реалізації:
| Компонент | Варіанти |
|---|---|
| Vector DB | Pinecone, Weaviate, Qdrant, pgvector |
| Embeddings | OpenAI text-embedding-3-small, Cohere, Ollama (self-hosted) |
| LLM | GPT-4o-mini, Claude 3.5 Haiku |
| Оркестрація | LangChain.js, LlamaIndex, або без фреймворка |
Індексація бази знань
import OpenAI from 'openai';
import { QdrantClient } from '@qdrant/js-client-rest';
const openai = new OpenAI();
const qdrant = new QdrantClient({ url: 'http://localhost:6333' });
// Підготовка колекції
await qdrant.createCollection('support-docs', {
vectors: { size: 1536, distance: 'Cosine' },
});
// Індексація статей
async function indexArticle(article) {
// Розділяємо на чанки по 500 токенів з перекриттям 100
const chunks = splitIntoChunks(article.content, { size: 500, overlap: 100 });
const embeddings = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: chunks.map(c => c.text),
});
const points = chunks.map((chunk, i) => ({
id: generateId(),
vector: embeddings.data[i].embedding,
payload: {
text: chunk.text,
articleId: article.id,
articleTitle: article.title,
category: article.category,
url: article.url,
},
}));
await qdrant.upsert('support-docs', { points });
}
Пошук та генерація відповіді
async function answerQuestion(userId, question) {
// Контекст користувача з БД
const user = await db.users.findById(userId);
const userContext = `
Користувач: ${user.name}
Тариф: ${user.plan}
Дата реєстрації: ${user.createdAt}
Останні 3 звернення: ${user.recentTickets.join(', ')}
`;
// Векторний пошук
const queryEmbedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: question,
});
const results = await qdrant.search('support-docs', {
vector: queryEmbedding.data[0].embedding,
limit: 4,
score_threshold: 0.75, // Ігнорувати нерелевантне
});
const context = results.map(r =>
`[${r.payload.articleTitle}](${r.payload.url})\n${r.payload.text}`
).join('\n\n---\n\n');
// Генерація відповіді
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
stream: true,
messages: [
{
role: 'system',
content: `Ви асистент технічної підтримки.
Відповідайте ТІЛЬКИ на основі наданої документації.
Якщо відповіді немає в документації, скажіть це явно та запропонуйте створити квиток.
Завжди посилайтеся на джерело (посилання на статтю).
Контекст користувача:
${userContext}`,
},
{
role: 'user',
content: `Запитання: ${question}\n\nРелевантна документація:\n${context}`,
},
],
max_tokens: 600,
temperature: 0.2,
});
return {
stream: response,
sources: results.map(r => ({ title: r.payload.articleTitle, url: r.payload.url })),
};
}
Handoff до живого оператора
Коли бот не може допомогти — передача до оператора:
const ESCALATION_TRIGGERS = [
'хочу поговорити з людиною',
'оператор',
'скарга',
'повернення грошей',
'видалити акаунт',
];
function shouldEscalate(message, confidenceScore) {
const lowerMessage = message.toLowerCase();
const hasKeyword = ESCALATION_TRIGGERS.some(t => lowerMessage.includes(t));
const lowConfidence = confidenceScore < 0.6;
return hasKeyword || lowConfidence;
}
async function handleMessage(userId, message) {
const { answer, confidence, sources } = await answerQuestion(userId, message);
if (shouldEscalate(message, confidence)) {
await createSupportTicket(userId, message);
return {
type: 'escalation',
message: 'Передаю ваш запит оператору. Середній час відповіді — 2 години.',
ticketId: ticket.id,
};
}
await logConversation(userId, message, answer);
return { type: 'answer', content: answer, sources };
}
Оновлення бази знань
Коли документація оновлюється — потрібно переіндексувати:
// Webhook від CMS при оновленні статті
app.post('/webhooks/docs-updated', async (req, res) => {
const { articleId, action } = req.body;
if (action === 'delete') {
await qdrant.delete('support-docs', {
filter: { must: [{ key: 'articleId', match: { value: articleId } }] },
});
} else {
const article = await fetchArticle(articleId);
// Видаляємо старі чанки
await qdrant.delete('support-docs', {
filter: { must: [{ key: 'articleId', match: { value: articleId } }] },
});
// Переіндексуємо
await indexArticle(article);
}
res.json({ ok: true });
});
Аналітика та поліпшення
Логуємо всі діалоги та збираємо зворотний зв'язок:
// Після кожної відповіді пропонуємо оцінку
function renderFeedback(messageId) {
return (
<div className="feedback">
<button onClick={() => submitFeedback(messageId, 'helpful')}>Допомогло</button>
<button onClick={() => submitFeedback(messageId, 'not-helpful')}>Не допомогло</button>
</div>
);
}
// Тижневий звіт: топ-20 запитань без хорошої відповіді
SELECT question, COUNT(*) as count
FROM support_conversations
WHERE feedback = 'not-helpful'
GROUP BY question
ORDER BY count DESC
LIMIT 20;
Терміни
- RAG-асистент з базою знань (до 500 статей) — 5–7 днів
- Персоналізація за контекстом користувача — плюс 1–2 дні
- Handoff до оператора + система квитків — плюс 2–3 дні
- Аналітика діалогів та дашборд — плюс 3–4 дні







