AI-система корпоративного пошуку

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1 послугУсі 1566 послуг
AI-система корпоративного пошуку
Середня
~2-4 тижні
Часті питання
Напрямки AI-розробки
Етапи розробки AI-рішення
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1217
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    852
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1046
  • image_logo-advance_0.png
    Розробка логотипу компанії B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    823

AI Enterprise Search: корпоративный интеллектуальный поиск

В компании из 500 человек знания хранятся в 7–12 системах одновременно: Confluence, SharePoint, Jira, корпоративная почта, 1C, внутренние CRM, файловые сервера. Сотрудник тратит в среднем 2,5 часа в день на поиск информации — это не метафора, это данные McKinsey. Ключевая проблема не в том, что данные есть, а в том, что найти их через обычный keyword-поиск невозможно: документ называется «Регламент_v3_final_FINAL2», а запрос — «как оформить командировку».

AI Enterprise Search решает это через семантическое понимание запроса и гибридный поиск по всем источникам одновременно.

Архитектура системы

[Источники данных]
Confluence, SharePoint, Jira,
Email, 1C, CRM, FileServer
         ↓
[Indexing Pipeline]
  Коннекторы → Парсинг → Chunking
  → Embedding (E5-large / BGE-M3)
  → Vector DB (Qdrant) + BM25 index
         ↓
[Search Engine]
  Query Understanding (LLM)
  → Hybrid Search (векторный + keyword)
  → Re-ranking (CrossEncoder)
  → Answer Generation (GPT-4o / Claude)
         ↓
[Интерфейс]
  Web UI, Slack-бот, API

Гибридный поиск: почему только vector недостаточно

Чистый векторный поиск хорошо работает на семантически похожих запросах, но плохо — на точных совпадениях: артикулах, именах, датах. Запрос «договор с ООО "Ромашка" от 12.03.2024» даст лучший результат через BM25, а «порядок действий при инциденте с утечкой данных» — через векторный поиск.

from qdrant_client import QdrantClient
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer, CrossEncoder
import numpy as np

class HybridSearchEngine:
    def __init__(self):
        self.dense_model = SentenceTransformer("intfloat/multilingual-e5-large")
        self.reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
        self.qdrant = QdrantClient(url="http://localhost:6333")
        self.bm25_index = None
        self.doc_store = {}

    def search(self, query: str, top_k: int = 20, final_k: int = 5) -> list[dict]:
        # Dense search
        query_emb = self.dense_model.encode(
            f"query: {query}",  # E5-формат
            normalize_embeddings=True
        )
        dense_results = self.qdrant.search(
            collection_name="enterprise_docs",
            query_vector=query_emb.tolist(),
            limit=top_k
        )

        # Sparse search (BM25)
        bm25_scores = self.bm25_index.get_scores(query.lower().split())
        top_bm25_ids = np.argsort(bm25_scores)[-top_k:][::-1]

        # Merge результатов (RRF - Reciprocal Rank Fusion)
        merged = self._reciprocal_rank_fusion(dense_results, top_bm25_ids)

        # Cross-encoder reranking
        pairs = [(query, self.doc_store[doc_id]["text"]) for doc_id in merged[:top_k]]
        rerank_scores = self.reranker.predict(pairs)
        reranked = sorted(
            zip(merged[:top_k], rerank_scores),
            key=lambda x: x[1],
            reverse=True
        )

        return [self.doc_store[doc_id] for doc_id, _ in reranked[:final_k]]

    def _reciprocal_rank_fusion(self, dense: list, sparse: list, k: int = 60) -> list:
        scores = {}
        for rank, item in enumerate(dense):
            doc_id = item.id
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
        for rank, doc_id in enumerate(sparse):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
        return sorted(scores, key=scores.get, reverse=True)

Понимание запроса и генерация ответа

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

class EnterpriseSearchAssistant:
    def __init__(self, search_engine: HybridSearchEngine):
        self.search = search_engine
        self.llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)

    def answer(self, query: str, user_context: dict) -> dict:
        # Расширение запроса для лучшего поиска
        expanded_query = self._expand_query(query, user_context)

        # Поиск по всем источникам
        results = self.search.search(expanded_query, final_k=7)

        if not results:
            return {"answer": "Документы по вашему запросу не найдены.", "sources": []}

        # Генерация ответа с цитатами
        context = "\n\n".join([
            f"[{i+1}] {r['title']} ({r['source']})\n{r['text'][:500]}"
            for i, r in enumerate(results)
        ])

        prompt = f"""Ответь на вопрос сотрудника на основе документов компании.
Используй только предоставленные документы. Цитируй источники [1], [2] и т.д.
Если ответ неполный — скажи об этом.

Вопрос: {query}
Документы:
{context}"""

        answer = self.llm.invoke(prompt).content
        return {
            "answer": answer,
            "sources": [{"id": i+1, "title": r["title"], "url": r["url"]}
                        for i, r in enumerate(results)]
        }

Индексация источников

Для каждого источника — отдельный коннектор с инкрементальным обновлением:

Источник Коннектор Частота индексации Особенности
Confluence REST API (spaces, pages) Каждые 30 мин Обработка разметки Confluence
SharePoint Microsoft Graph API Каждый час Поддержка Word/PDF/PPTX
Jira REST API Каждые 15 мин Тикеты + комментарии
Email IMAP/Exchange Realtime (webhooks) Только отправленные/полученные
Файловый сервер Inotify / polling При изменении PDF, DOCX, XLSX, TXT

Кейс из практики: производственный холдинг, 1200 сотрудников. Индексировали 340 000 документов из Confluence, SharePoint и самописной СЭД. Время загрузки индекса — 18 часов (однократно). Регулярное обновление — дельта за 30 минут, ~200 документов, занимает 4 минуты. Средняя точность топ-3 ответов (оценка HR-командой по выборке 500 запросов): 78% релевантных результатов против 41% у прежнего Elasticsearch keyword-поиска.

Access Control: безопасность на уровне поиска

Критическая деталь enterprise-поиска — пользователь должен видеть только те документы, к которым у него есть доступ. Реализуется через metadata-фильтры в Qdrant:

# При поиске фильтруем по правам пользователя
results = qdrant.search(
    collection_name="enterprise_docs",
    query_vector=query_emb,
    query_filter=Filter(
        must=[
            FieldCondition(
                key="allowed_groups",
                match=MatchAny(any=user_groups)
            )
        ]
    ),
    limit=20
)

Сроки

  • Пилот (1–2 источника, ~50 000 документов): 4–6 недель
  • Полная система с 5–8 источниками и UI: 3–4 месяца
  • Настройка ACL и доступов: +2–3 недели