- BrainTools - https://www.braintools.ru -

Фабрикация диалога и теневой контекст для ИИ — проектируем себе друга

Чем отличается беседа с другом от разговора со случайным попутчиком в долгом путешествии? Как минимум объемом общего контекста. Там, где первый всё поймёт с пары фраз, у второго останутся только вопросы.

Книги из детства обещали мне ИИ в роли друга, напарника, наставника. Но так уж получилось, что сейчас LLM — это случайный знакомый с тяжёлой формой амнезии. Давайте попробуем это исправить.

Сообщения в чате с ИИ это лишь абстракция на уровне пользовательского интерфейса и на самом деле чтобы получить ответ на одно сообщение нужно отправить на вход LLM всю историю чата. Очень хорошо и подробно об этом рассказал пользователь Propan671 [1] вот тут — Контекст и управление им [2].

Но раз история отправляется каждый раз заново, то значит мы можем и редактировать её каждый раз перед тем как отправить. То есть фактически мы можем переписывать и свои, и прошлые ответы ИИ, создавая идеальный контекст для нашего нового сообщения. Этим мы и воспользуемся.

Теневой контекст: срезы памяти вместо бесконечного окна

Представьте, что у нас есть короткий, но запутанный лог диалога, где мы успели поговорить о работе, хобби и технологиях. В сыром виде (в формате JSON) эта история выглядит как череда сообщений:

```
[
  {
    "role": "user",
    "text": "Я собираю сервер дома. Хочу накатить туда Llama-3. 
             Вечером иду в зал, надо пожать сотку."
  },
  {
    "role": "assistant",
    "text": "Отличный план! Для Llama-3 тебе понадобится минимум 
             24GB видеопамяти. А сотка — хороший рабочий вес, береги связки."
  },
  {
    "role": "user",
    "text": "У меня только RTX 3060 на 12 гигов. Что посоветуешь 
             из упражнений на грудь?"
  },
  {
    "role": "assistant",
    "text": "На 12 гигов Llama не влезет, смотри в сторону Gemma-300M. 
             Для груди делай жим гантелей под углом."
  }
]
```

Шаг 1: Нарезка и изоляция

Сначала мы разбиваем этот лог на отдельные предложения и складываем их в два независимых массива: один для пользователя, другой для ассистента.

user_history = [
    "Я собираю сервер дома.", # индекс 0
    "Хочу накатить туда Llama-3.", # индекс 1
    "Вечером иду в зал, надо пожать сотку.", # индекс 2
    "У меня только RTX 3060 на 12 гигов.", # индекс 3
    "Что посоветуешь из упражнений на грудь?" # индекс 4
]

model_history = [
    "Отличный план!", # индекс 0
    "Для Llama-3 тебе понадобится минимум 24GB видеопамяти.", # индекс 1
    "А сотка — хороший рабочий вес, береги связки.", # индекс 2
    "На 12 гигов Llama не влезет, смотри в сторону Gemma-300M.", # индекс 3
    "Для груди делай жим гантелей под углом." # индекс 4
]

Шаг 2: Разметка

Теперь мы пропускаем эти предложения через классификатор. Какой и как его сделать решим потом, сейчас подрядим для этого быструю и дешевую LLM. Она читает каждое предложение и вешает на него тег. По сути, мы создаем многомерную маску (индексы) для наших массивов:

# Какие индексы относятся к железу и ИИ?
tags = {
    "user": {
        "tech": [0, 1, 3],
        "sport": [2, 4]
    },
    "model": {
        "tech": [1, 3],
        "sport": [2, 4],
        "noise": [0]  # "Отличный план!" - не несет фактов
    }
}

Шаг 3: Синтез теневого контекста

Теперь, если пользователь спрашивает: “Так какую модель мне в итоге поставить?”, мы не скармливаем LLM информацию про штангу и жим лежа. Мы берем только те индексы, которые лежат в tags['user']['tech'] и tags['model']['tech'] и нейросеть получает идеально чистый теневой контекст:

```
[
  {
    "role": "user", 
    "text": "Я собираю сервер дома. Хочу накатить туда Llama-3. 
             У меня только RTX 3060 на 12 гигов."
  },{
    "role": "assistant", 
    "text": "Для Llama-3 тебе понадобится минимум 24GB видеопамяти. 
             На 12 гигов Llama не влезет, смотри в сторону Gemma-300M."
  },{
    "role": "user", 
    "text": "Так какую модель мне в итоге поставить?"
  }
]
```

Для LLM этого диалога про спорт никогда не существовало. Она видит только плотный, логичный разговор двух инженеров. Именно так мы сжимаем мегабайты «водянистой» переписки в идеальный слепок памяти [3].

Шаг 4: Расставляем всё по своим местам

Почему теневой? Потому что пользователю не обязательно знать о всех этих махинациях с историей диалога. Бэкенд сам справится с этой задачей, а пользователь может вести свой диалог, как обычно:

```
[
  {"role": "user","text": "..."},
  {"role": "assistant","text": "..."},
  {"role": "user","text": "..."},
  {
    "role": "assistant",
    "text": "На 12 гигов Llama не влезет, смотри в сторону Gemma-300M. 
             Для груди делай жим гантелей под углом."
  },{
    "role": "user",
    "text": "Так какую модель мне в итоге поставить?"
  },{
    "role": "assistant",
    "text": "Понятия не имею. Посмотри что я тебе 
             предложил раньше - Gemma-300M. 
             Это тебе за шутку с GPT-OSS-120B"
  }
]
```

Зачем переписывать сообщения ИИ?

Где-то в глубинах сервера массив сообщений конкатенируется в строку и уже в таком виде подаётся на вход нейронной сети.

<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
What's the capital of France?<|im_end|>
<|im_start|>assistant

В примере выше показаны специальные управляющие токены из стандарта ChatML, разработанного OpenAI. Но это не способ форматирования — это архитектурное решение. На этапе обучения [4] модели оно позволяет сформировать внутри трансформера разное распределение весов для входящих запросов и собственных ответов.

В каком-то смысле в процессе диалога assistant и user могут думать каждый о своём.

Как это можно использовать? Механика инъекции фактов

На скриншотах показано как мы вписываем в историю диалога информацию, которая во время обучения модели не использовалась. В данном примере изменено авторство второго сообщения с user на model и, как мы видим, в первом случае ИИ нас поправил, во втором поверил себе на слово.

В роли эксперта любезно согласился выступить Gemini 3.1 pro.

В роли эксперта любезно согласился выступить Gemini 3.1 pro.
Скрытый текст

Кстати можно сказать мы только что переизобрели RAG, но без звонкой аббревиатуры вряд ли кто‑то обратит на это внимание [5]. Идея же простая — вместо инструкций, подсказок с релевантной информацией мы просто создаем диалог в котором факты озвучены самой моделью. Но будьте осторожны, LLM не всегда так просто поддается как на этом примере.

И этой информацией, фактами, может быть что угодно — берем внешний источник (API календаря, лог из таск‑трекера, письма), вытаскиваем оттуда факт и заворачиваем его в тег assistant. Модель принимает этот факт не как вводную инструкцию, с которой можно поспорить, а как собственное воспоминание:

```
[
  { // 1. Скрытый блок. Сгенерирован бэкендом на основе последних писем.
    "role": "assistant", 
    "text": "В последнем письме Милена обозначила, что
             на следующем этапе интервью на позицию AI Lead мне 
             нужно защитить концепцию Compound AI System с 
             использованием SLM."
  }, 
  { // 2. Реальный запрос пользователя
    "role": "user", 
    "text": "Я сейчас буду делать презентацию для звонка. 
             Набросай структуру из трех главных слайдов."
  }
]
```

От теории к вопросам практической реализации

Как собрать из этой концепции рабочий код? Не знаю. Реальность сильно сложнее приведенных выше примеров. Тем не менее, как мне кажется, это уже инженерия: связать векторную базу, классификатор и логику [6] подмены контекста.

Из всех известных мне поисковых движков для создания базы с сообщениями самым перспективным выглядит Apache Lucene с серверными оболочками типа Elasticsearch и Solr. И вот почему, основные возможности движков на базе Lucene:

  • Инвертированный индекс: Базовая структура данных для быстрого поиска. Текст разбивается на отдельные слова (токены), и для каждого слова сохраняется список документов, в которых оно встречается.

  • Морфологический анализ: Поддержка стемминга (усечение слова до основы), лемматизации (приведение к словарной форме), работы с синонимами, удаления стоп-слов и генерация N-грамм.

  • Ранжирование: Оценка релевантности найденных документов запросу. Современные версии по умолчанию используют алгоритм BM25, который учитывает частоту термина в документе и редкость термина во всей базе.

  • Нечеткий поиск (Fuzzy Search): Поиск с учетом возможных опечаток пользователя, основанный на вычислении расстояния Левенштейна.

  • Фасетирование и агрегации: Динамическая группировка результатов по категориям, атрибутам или диапазонам (например, подсчет количества товаров заданного цвета для бокового фильтра в интернет-магазине).

  • Векторный поиск (kNN): Поиск по семантической близости с использованием векторных представлений (эмбеддингов), что позволяет искать по смыслу, а не по точному совпадению слов.

Вариант 1. Атомарная индексация + сборка на лету

Мы не пытаемся заранее склеить предложения в группы. Мы делаем каждое предложение отдельным документом, но связываем их метаданными.

  1. Запись: Сообщение разбивается на отдельные предложения (через NLP-библиотеку вроде spaCy или NLTK).

  2. Эмбеддинг: Каждое предложение превращается в вектор.

  3. Индексация: В Elasticsearch улетают документы вида: {"message_id": 123, "sentence_id": 5, "text": "...", "vector": [...]}

  4. Поиск и сборка: Когда приходит новый запрос, мы ищем топ-N самых похожих предложений в базе и надеемся на то, что получится связный текст и диалог.

Вариант 2. Векторная кластеризация при записи

Смысловые срезы формируются автоматически до попадания в базу, опираясь на близость векторов, а не на абзацы или предложения.

  1. Нарезка и векторизация: Сообщение бьется на предложения, для каждого генерируется вектор.

  2. Кластеризация: Алгоритм (например, DBSCAN или матрица косинусного сходства) сравнивает векторы всех предложений внутри одного сообщения между собой. Если 1-е, 5-е и 7-е предложения близки друг к другу в многомерном пространстве — они объединяются в один кластер.

  3. Индексация: В Elasticsearch улетает уже готовый смысловой срез: {"slice_text": "Предл 1. Предл 5. Предл 7.", "vector": [усредненный вектор кластера]}.

Вариант 3. SLM как маршрутизатор смыслов

Если абзацы не сработают, попробуем делегировать задачу выделения логических нитей небольшой, быстрой языковой модели (Small Language Model).

  1. Анализ: Входящее сообщение отдается локальной модели с системным промптом: «Разбей текст на логические независимые мысли. Для каждой мысли верни массив индексов предложений. Формат строго JSON».

  2. Сборка: Скрипт получает массивы вида [1, 5, 7] и [1, 3, 4], физически склеивает эти предложения в строки.

  3. Индексация: Полученные строки прогоняются через модель эмбеддингов и сохраняются в Elasticsearch как отдельные смысловые документы, ссылающиеся на родительский message_id

Итого:

План такой — из всей истории диалога, пусть это десятки мегабайт текста, с помощью комбинации векторного и символьного поиска синтезировать из отдельных предложений сообщений assistant и user консистентную, но в реальности не существовавшую версию диалога, в которой будет собран весь актуальный контекст для текущего запроса пользователя. 

Эту версию мы будем отправлять в LLM вместо настоящей, а результат генерации добавлять в настоящую историю, видимую пользователю. И так мы будем делать каждый раз. 

Если реализуемо, то получим Stateful LLM с неограниченной памятью умещающейся в контекстное окно ~10 000–20 000 токенов.

Когда релиз? Вечером. Не сегодня, в какой‑то из дней

С моими навыками реализация всего этого будет не быстрой. Но ИИ‑друга отвечающего сообщениями типа: «Ну что, опять как в прошлый раз?» мне уж слишком хочется, а значит для меня история на этой статье не заканчивается.

Автор: WantToBelieve

Источник [7]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/31150

URLs in this post:

[1] Propan671: https://habr.com/ru/users/Propan671/

[2] Контекст и управление им: https://habr.com/ru/articles/1029326/#context

[3] памяти: http://www.braintools.ru/article/4140

[4] обучения: http://www.braintools.ru/article/5125

[5] внимание: http://www.braintools.ru/article/7595

[6] логику: http://www.braintools.ru/article/7640

[7] Источник: https://habr.com/ru/articles/1042592/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1042592

www.BrainTools.ru

Rambler's Top100