Как индексировать чат-историю для RAG и не потерять контекст
Slack, Teams, Telegram — тысячи сообщений в день. Инженерные решения, баги, обсуждения архитектуры — всё тонет в чатах. Поиск вручную невозможен, а просто запихать логи в RAG — шум. Как извлечь знания без потери контекста и с соблюдением приватности? Мы построили систему индексации чатов, которая решает эти задачи. В одном из проектов мы проиндексировали 500 000 сообщений из Slack за три дня, после чего разработчики стали находить нужную информацию за секунды вместо часов. Экономия времени — до 70%, как показала наша практика. По нашим данным, 73% ценных инженерных решений остаются только в чатах. При смене состава команды этот контекст теряется — индексация чат-истории для RAG сохраняет его для новых участников. Наш метод тематической чанкинг стратегии в 1.5-2 раза точнее фиксированных окон при поиске релевантных диалогов.
Проблемы, которые решаем
- Неструктурированность: сообщения разбиты на треды, содержат emoji, упоминания, мемы. Нужно выделить смысл, отбросив шум. При этом объём данных может достигать терабайтов — без автоматизации не обойтись. Используем sentence-transformers для выделения семантики — это уменьшает размер индекса на 40% по сравнению с хранением сырых логов.
- Потеря контекста: одно обсуждение может растянуться на дни. Тематический чанкинг — единственный способ сохранить связность. Фиксированный размер окна обрывает диалог на полуслове, что даёт прирост precision@k на 20-30% при нашем подходе.
- Конфиденциальность: имена, почты, ссылки — всё это нужно анонимизировать до индексации. Особенно строгие требования в регулируемых отраслях (финансы, медицина). Мы используем регулярные выражения и NER для замены персональных данных на анонимные идентификаторы.
- Политики хранения: сообщения старше N дней удаляются, это влияет на полноту базы знаний. Нужно учитывать retention политики при проектировании пайплайна. Индекс автоматически обновляется при изменении исходных данных, чтобы база знаний оставалась актуальной.
Как мы это делаем: стек и кейс Slack
Используем sentence-transformers для эмбеддингов, pgvector для хранения векторов, LangChain для оркестрации. Retrieval-Augmented Generation (RAG) — ключевая парадигма, на которой построена система. Ниже — пример интеграции Slack с пагинацией и восстановлением тредов из нашей практики: для одного клиента мы проиндексировали 500 000 сообщений за 3 дня.
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
class SlackIndexer:
def __init__(self, token: str):
self.client = WebClient(token=token)
def get_messages(self, channel_id: str,
oldest: float = None,
limit: int = 1000) -> list[dict]:
messages = []
cursor = None
while True:
params = {
'channel': channel_id,
'limit': 200,
'oldest': oldest
}
if cursor:
params['cursor'] = cursor
result = self.client.conversations_history(**params)
messages.extend(result['messages'])
if not result.get('has_more') or len(messages) >= limit:
break
cursor = result['response_metadata']['next_cursor']
return messages
def reconstruct_thread(self, channel_id: str,
thread_ts: str) -> list[dict]:
"""Загрузка полного треда"""
result = self.client.conversations_replies(
channel=channel_id,
ts=thread_ts
)
return result['messages']
def messages_to_document(self, messages: list[dict],
channel_name: str) -> dict:
"""Конвертация набора сообщений в индексируемый документ"""
# Фильтрация служебных сообщений
relevant = [
m for m in messages
if m.get('type') == 'message'
and not m.get('subtype') # Убираем channel_join, bot_message и т.д.
and len(m.get('text', '')) > 20
]
if not relevant:
return None
# Группировка в сессии (сообщения в течение 1 часа)
sessions = self._group_into_sessions(relevant, gap_hours=1)
documents = []
for session in sessions:
text = '\n'.join([
f"[{self._get_username(m['user'])}]: {m['text']}"
for m in session
if m.get('user')
])
# Разрешение ссылок на пользователей и каналы
text = self._resolve_mentions(text)
documents.append({
'text': text,
'channel': channel_name,
'timestamp_start': session[0]['ts'],
'timestamp_end': session[-1]['ts'],
'participants': list(set(m.get('user') for m in session if m.get('user'))),
'message_count': len(session)
})
return documents
Почему тематическая чанкинг точнее фиксированных окон?
| Характеристика | Фиксированные окна | Тематическая чанкинг (наша) |
|---|---|---|
| Размер чанка | Фиксированный (например 512 токенов) | Адаптивный, зависит от смены темы |
| Сохранность контекста | Низкая (обрывает середину диалога) | Высокая (сохраняет всю тему) |
| Релевантность поиска | Средняя | Высокая (чанк = законченная мысль) |
| Сложность реализации | Низкая | Средняя (требует эмбеддингов и порога схожести) |
Мы используем второй подход — он даёт прирост precision@k на 20-30% по сравнению с фиксированной нарезкой. По данным инженерного отчета Slack, более 70% рабочих обсуждений происходит в каналах.
class ChatChunker:
def chunk_by_topic(self, messages: list[dict],
similarity_threshold: float = 0.6) -> list[list]:
"""Разбивка на тематические группы, а не по фиксированному размеру"""
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
texts = [m.get('text', '') for m in messages]
embeddings = model.encode(texts)
# Разбивка там, где тема резко меняется
chunks = [[messages[0]]]
for i in range(1, len(messages)):
sim = np.dot(embeddings[i], embeddings[i-1]) / (
np.linalg.norm(embeddings[i]) * np.linalg.norm(embeddings[i-1])
)
if sim < similarity_threshold:
chunks.append([])
chunks[-1].append(messages[i])
return chunks
Как анонимизировать диалоги без потери смысла?
Конфиденциальность — ключевой риск. Наши инженеры реализуют гибкую замену персональных данных:
class ChatAnonymizer:
def anonymize(self, text: str, user_mapping: dict) -> str:
"""Замена имён пользователей на анонимные ID"""
for real_name, anon_id in user_mapping.items():
text = text.replace(f"@{real_name}", f"@user_{anon_id}")
text = text.replace(real_name, f"[User {anon_id}]")
return text
Для корпоративного Slack индексация должна: исключать личные переписки (DM), соблюдать настройки retention политики (сообщения старше N дней удаляются), предоставлять возможность исключения конкретных каналов или пользователей по их запросу. Это особенно важно для соответствия требованиям GDPR и другим регуляциям.
Что входит в работу: этапы и сроки
| Этап | Описание | Ориентировочный срок |
|---|---|---|
| Аудит источников | Карта каналов, оценка объёмов (сообщения/месяц), политики хранения | 2-3 дня |
| Проектирование | Выбор платформ, правила анонимизации, чанкинг стратегия | 3-5 дней |
| Реализация | Код для импорта, векторизации, загрузки в векторную БД | 1-4 недели |
| Тестирование | Measure precision/recall на репрезентативных запросах, оптимизация порогов | 1 неделя |
| Деплой и мониторинг | Латентность p99, coverage (доля проиндексированных сообщений) | 1 неделя |
| Документация и обучение | Как пользоваться, как обновлять, как исключать данные | 2-3 дня |
Ориентировочные сроки
От 2 недель (один источник, до 100K сообщений) до 2 месяцев (много платформ, сложные правила анонимизации). Свяжитесь с нами — мы оценим ваш проект за один рабочий день.
Пример запроса к проиндексированному чату
"Как мы решали проблему с таймаутами при миграции PostgreSQL?" — система найдёт соответствующий тред в Slack за прошлый год с кодом и ссылкой на тикет.Мы имеем 5+ лет опыта в RAG и реализовали 20+ проектов по индексации корпоративных чатов. Получите консультацию — поможем превратить хаос переписок в работающую базу знаний.







