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 недель.







