AI-система поиска по внутренней документации и базам знаний

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1Все 1566 услуг
AI-система поиска по внутренней документации и базам знаний
Средний
~2-4 недели
Часто задаваемые вопросы

Направления AI-разработки

Этапы разработки AI-решения

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    901
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1119
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    853

AI-поиск по внутренней документации и базе знаний

Внутренняя база знаний компании — это часто несколько сотен статей в Confluence или Notion, которые устарели на 30–40%, написаны разными авторами без единого стиля и плохо связаны между собой. Keyword-поиск по ним работает посредственно: сотрудник пишет «как настроить VPN», а нужная статья называется «Инструкция по подключению к корпоративной сети».

AI-поиск решает проблему через семантическое понимание, автоматически находит связанные материалы и генерирует ответ с цитатами — не «вот 10 статей, разбирайся», а «вот ответ на твой вопрос из документа №3, см. также раздел 4.2 в документе №7».

Стек и архитектура для документационного поиска

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.postprocessor import SentenceTransformerRerank
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
import qdrant_client

# Модель эмбеддингов — мультиязычная для RU/EN документации
embed_model = HuggingFaceEmbedding(
    model_name="intfloat/multilingual-e5-large",
    embed_batch_size=32
)

# Чанкинг с перекрытием — важен для документации
splitter = SentenceSplitter(
    chunk_size=512,
    chunk_overlap=64,
    paragraph_separator="\n\n"
)

# Reranker для финального ранжирования
reranker = SentenceTransformerRerank(
    model="cross-encoder/ms-marco-MiniLM-L-6-v2",
    top_n=5
)

# Построение индекса
client = qdrant_client.QdrantClient(url="http://localhost:6333")
vector_store = QdrantVectorStore(client=client, collection_name="kb_docs")

index = VectorStoreIndex.from_vector_store(
    vector_store=vector_store,
    embed_model=embed_model
)

query_engine = RetrieverQueryEngine(
    retriever=VectorIndexRetriever(index=index, similarity_top_k=15),
    node_postprocessors=[reranker],
)

Главная проблема: чанкинг документации

Документация устроена иерархически — раздел, подраздел, параграф. Наивный чанкинг по 512 токенов разрезает контекст в неудачных местах: ответ на вопрос «что делать если X» может находиться в заголовке раздела и трёх параграфах под ним, а чанк содержит только один параграф без заголовка.

Решение — parent-child chunking:

from llama_index.core.node_parser import HierarchicalNodeParser
from llama_index.core.retrievers import AutoMergingRetriever
from llama_index.core.storage.docstore import SimpleDocumentStore

# Иерархическое разбиение: крупные чанки для контекста, мелкие для поиска
hier_parser = HierarchicalNodeParser.from_defaults(
    chunk_sizes=[2048, 512, 128]  # большой → средний → малый
)

nodes = hier_parser.get_nodes_from_documents(documents)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)

# При поиске находим мелкий чанк, но отдаём родительский контекст
retriever = AutoMergingRetriever(
    vector_retriever,
    storage_context,
    verbose=True,
    simple_ratio_thresh=0.3  # если 30% мелких чанков из родителя → берём родителя
)

На реальных тестах: AutoMerging Retriever даёт faithfulness +12% и relevance +18% по сравнению с flat chunking на корпусе технической документации (тест на 300 вопросов, оценка через RAGAS).

Автоматическое обновление индекса

class DocumentationIndexer:
    def __init__(self, confluence_client, vector_store):
        self.confluence = confluence_client
        self.index = vector_store
        self.last_indexed = {}  # page_id → last_modified timestamp

    async def incremental_update(self):
        """Обновляет только изменённые документы"""
        all_pages = self.confluence.get_all_pages(space_key="KB")

        for page in all_pages:
            page_id = page["id"]
            modified = page["version"]["when"]

            if self.last_indexed.get(page_id) == modified:
                continue  # не изменился

            # Удаляем старые чанки этой страницы
            self.index.delete(filter={"page_id": page_id})

            # Парсим и индексируем заново
            content = self.confluence.get_page_body(page_id)
            nodes = self._parse_and_chunk(content, page)
            self.index.add(nodes)

            self.last_indexed[page_id] = modified

        return {"updated": len([p for p in all_pages
                                 if self.last_indexed.get(p["id"]) != p["version"]["when"]])}

Slack-бот и web-интерфейс

В большинстве проектов основная точка входа — Slack-бот, не отдельный портал:

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=SLACK_BOT_TOKEN)

@app.message(re.compile(r"^/kb (.+)"))
def handle_kb_query(message, say, context):
    query = context["matches"][0]
    result = query_engine.query(query)

    blocks = [
        {"type": "section", "text": {"type": "mrkdwn",
         "text": f"*Ответ:*\n{result.response}"}},
        {"type": "section", "text": {"type": "mrkdwn",
         "text": f"*Источники:*\n" + "\n".join([
             f"• <{n.metadata['url']}|{n.metadata['title']}>"
             for n in result.source_nodes[:3]
         ])}}
    ]
    say(blocks=blocks)

Кейс: IT-компания, 200 человек, 1200 статей в Confluence. Среднее время ответа на типичный вопрос через Slack-бот — 1,4 сек. Accuracy (верный ответ в топ-1 по оценке команды, выборка 200 запросов) — 82%. Количество повторных вопросов в #general упало на 43% за первый месяц работы бота.

Сроки

  • Базовый RAG-поиск по Confluence/Notion: 2–3 недели
  • С Slack-интеграцией и обновлением: 3–5 недель
  • Полноценная система с аналитикой запросов и выявлением пробелов в документации: 6–8 недель