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

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-система конверсационного поиска
Средняя
~2-4 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI Conversational Search: диалоговый поиск с контекстом

Обычный поиск — stateless: каждый запрос независим. Conversational Search запоминает контекст диалога: «Покажи протоколы совещаний» → «А за прошлый квартал?» → «Только те, где упоминается Иванов» — система понимает, что каждый следующий запрос уточняет предыдущий, не требуя повторять весь контекст.

Ключевая техника: Query Rewriting

Главная инженерная проблема conversational search — ambiguous follow-up queries. Запрос «А за прошлый квартал?» бессмысленен без предыдущего контекста. Нужно переформулировать его в standalone-запрос перед поиском.

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
from dataclasses import dataclass, field

@dataclass
class ConversationState:
    session_id: str
    history: list = field(default_factory=list)  # [(query, answer), ...]
    last_entities: list[str] = field(default_factory=list)
    last_time_range: tuple = field(default_factory=tuple)

class ConversationalSearchEngine:
    REWRITE_PROMPT = """Ты — система переформулировки запросов.
Преобразуй уточняющий запрос в полный самодостаточный запрос на основе истории диалога.

История:
{history}

Текущий запрос пользователя: {query}

Правила:
- Если запрос самодостаточный — верни его без изменений
- Если содержит "это", "там", "тот же", "ещё раз" — расшифруй из контекста
- Сохрани все ограничения из предыдущих запросов, если новый их не снимает
- Верни ТОЛЬКО переформулированный запрос, без пояснений"""

    def __init__(self, search_engine, llm: ChatOpenAI):
        self.search = search_engine
        self.llm = llm

    def search_with_context(
        self,
        query: str,
        state: ConversationState
    ) -> dict:
        # Step 1: переформулируем запрос с учётом истории
        standalone_query = self._rewrite_query(query, state.history)

        # Step 2: поиск по переформулированному запросу
        results = self.search.search(standalone_query, final_k=6)

        # Step 3: генерация ответа с историей как контекстом
        answer = self._generate_answer(query, standalone_query, results, state)

        # Step 4: обновляем состояние
        state.history.append((query, answer["text"]))
        if len(state.history) > 10:
            state.history = state.history[-10:]  # sliding window

        return answer

    def _rewrite_query(self, query: str, history: list) -> str:
        if not history:
            return query

        history_text = "\n".join([
            f"User: {h[0]}\nAssistant: {h[1][:200]}..."
            for h in history[-3:]  # последние 3 обмена
        ])

        result = self.llm.invoke(
            self.REWRITE_PROMPT.format(history=history_text, query=query)
        )
        return result.content.strip()

Управление контекстным окном

При длинных сессиях история не вмещается в context window. Решение — иерархическое сжатие:

class ContextManager:
    def __init__(self, llm, max_history: int = 10):
        self.llm = llm
        self.max_history = max_history

    def compress_history(self, history: list) -> str:
        """Сжимаем старую историю в краткое резюме сессии"""
        if len(history) <= self.max_history:
            return None

        old_turns = history[:-self.max_history]
        summary_prompt = f"""Сожми историю диалога в краткое резюме (3–5 предложений).
Сохрани: ключевые сущности, временные периоды, фильтры которые упоминались.

История:
{chr(10).join([f'U: {h[0]}' for h in old_turns])}

Резюме:"""
        return self.llm.invoke(summary_prompt).content

Практический кейс

Юридическая компания, 120 юристов, база из 80 000 договоров и прецедентов. Типичный диалог:

Юрист: «Найди договоры аренды с нарушением срока уведомления»
→ rewritten: «договоры аренды где нарушен срок уведомления об изменении условий»

Юрист: «Только за 2023 год»
→ rewritten: «договоры аренды где нарушен срок уведомления об изменении условий, заключённые в 2023 году»

Юрист: «А где арендатор — юридическое лицо?»
→ rewritten: «договоры аренды где нарушен срок уведомления, 2023 год, арендатор — юридическое лицо»

После внедрения: среднее время поиска по базе прецедентов — с 18 минут до 2,5 минут. Юристы проводят 3–5 turns в среднем на сессию, 78% задач решается без выхода из диалога.

Персонализация под роль пользователя

ROLE_SYSTEM_PROMPTS = {
    "lawyer": "Ты — ассистент юриста. Цитируй договоры с указанием статей.",
    "hr": "Ты — HR-ассистент. Отвечай в терминах HR-процессов, ссылайся на регламенты.",
    "engineer": "Ты — технический ассистент. Включай технические детали, ссылки на документацию.",
}

Сроки: базовый conversational search с query rewriting — 3–4 недели; с персонализацией под роли и компрессией истории — 6–8 недель.