4. ИНТУИТИВНЫЙ КОНТУР: как я научил AI-ассистента «чувствовать» запрос до того, как он подумает
Как 3-5 слов-меток вместо 50 строк инструкций помогают LLM понимать жанр запроса, настроение пользователя и уровень собственной уверенности. Сравниваю с «Системой 1» Канемана. В прошлой статье про STM + МНЕМОС цикл сообщения был без этого слоя — сейчас покажу, где он реально стоит и что берёт из памяти.

Это четвёртая статья из серии «Как я собрал self-improving AI-ассистента». Предыдущие:

  1. Общий обзор архитектуры
  2. МНЕМОС: трёхуровневая долгосрочная память
  3. STM + МНЕМОС: двойная память


Проблема: промпт раздувался, LLM тонула

После STM + МНЕМОС ассистент уже помнил: оперативная память держала тему и цифры внутри диалога, долгосрочная — училась между диалогами. Но была другая беда: каждый запрос тянул за собой простыню инструкций.

пользователь → 5000 символов промпта → LLM генерирует ответ

Промпт рос: «не галлюцинируй», «будь вежливой», «проверяй цифры», «уточняй если не уверена», «используй женский род», «сначала проверь календарь», «для валют используй ЦБ РФ»... Правил становилось больше, LLM путалась, ответы становились «серединой по больнице».

А главное — LLM не знала контекста, который человек улавливает мгновенно:

  • Это знакомая тема или я спрашиваю впервые?
  • Пользователь злится или в хорошем настроении?
  • В этой теме уже были ошибки?
  • Это заказ на мебель или просьба посчитать спецификацию?
  • Нужен короткий ответ или глубокая проработка?

Память знает ответы на эти вопросы. Но она не говорит об этом LLM напрямую — она лишь кладёт данные в хранилище. Между памятью и LLM не хватало слоя, который превратит данные памяти в подсказку для модели. Я пытался компенсировать это инструкциями. Инструкции росли — промпт пух — LLM терялась. Замкнутый круг.

Решение: ассистент должен «пощупать» запрос ДО ответа

В психологии есть модель Канемана: 

  • Система 1 — быстрая, интуитивная, работает автоматически; 
  • Система 2 — медленная, рациональная, включается для сложных задач. У людей это две системы, работающие вместе.

У LLM всё наоборот — она всегда «Система 2»: получает весь контекст и медленно перебирает. А «Системы 1» у неё нет.

Я решил: сделаю ассистенту Систему 1. Назвал её Интуитивный Контур — слой, который за миллисекунды до ответа оценивает запрос и добавляет в промпт 3-5 коротких меток.

Где он стоит — наконец-то полная картина:

Пользователь пишет


STM L1: extract_topic → тема запроса
STM L1: should_anchor? → тема/цифры в якорь
STM L2: load_history → последние сообщения
МНЕМОС: search_experiences → топ-3 примера

Prompt Assembler: собирает system_prompt

Контур (1ms): читает STM + МНЕМОС → метки ←── вот здесь!

LLM отвечает (видит метки + промпт)

Sufler оценивает → STM/МНЕМОС учатся

В прошлой статье цикл заканчивался на Prompt Assembler. Контур — это слой между сборкой промпта и LLM. Он не хранит данные, как STM или МНЕМОС. Он их читает и превращает в метки-подсказки.

Ключевая идея: Контур не генерирует ответ. Он создаёт «атмосферу» для мышления. LLM получает не голый запрос, а 3-5 слов меток — и интерпретирует их сама.

Контур не хранит — он читает

Это самое важное. Контур — не еще одна система памяти, а, скажем так, "фундаментальная надстройка "над уже имеющимся модулями. Он берёт данные из STM и МНЕМОС и «озвучивает» их состояние для LLM:

Что читает контурОткудаЭто система
familiarity (знакома ли тема)ChromaDB, asyncМНЕМОС L1
microgoals (текущие темы диалога)якоряSTM L1
numanchors (глубина диалога)якоряSTM L1
risk_topics (где были ошибки)Sufler failsМНЕМОС L3
mood, streaks (настроение)Чутьёотдельный модуль
trust (доверие, самообучение)UserModel по SuflerМНЕМОС L1/L3
Контур


├── читает STM → темы, якоря, цифры (оперативная память)
├── читает МНЕМОС → знакомость, риск-темы, доверие (долгосрочная)
├── читает Чутьё → настроение, streak

└── отдаёт LLM → [УВЕР: HIGH] [ЖАНР: ЗАКАЗ] [РИСК: ПОДБОР]

Контур ничего не хранит — он «Система 1», которая заглядывает в обе памяти и коротко говорит LLM: «вот ситуация, реагируй».

StateVector: сердце контура

Всё, что Контур знает о запросе, — это объект StateVector. Семь полей, каждое — эвристика или данные, прочитанные из STM/МНЕМОС:

@dataclass

class StateVector:
familiarity: float = 0.5 # ← из МНЕМОС (ChromaDB async)
criticality: float = 0.0 # ← эвристики + Чутьё
complexity: float = 0.0 # ← эвристики + STM (анкоры)
mood: str = "нейтрально" # ← Чутьё
confidence: str = "MEDIUM" # ← familiarity + trust
genre: str = "диалог" # legacy keyword
method: str = "" # ← LLM-классификация
risk_topics: list = [] # ← МНЕМОС L3 (Sufler fails)
trust: float = 0.5 # ← самообучение по Sufler

def topromptfragment(self) -> str:
parts = [f"[УВЕР: {self.confidence}]"]
if self.genre != "диалог":
parts.append(f"[ЖАНР: {self.genre.upper()}]")
if self.mood not in ("нейтрально", "спокоен"):
parts.append(f"[МООД: {self.mood}]")
if self.risk_topics:
parts.append(f"[РИСК: {self.risk_topics[0]}]")
return " ".join(parts)

На выходе — фраза вида:

[УВЕР: HIGH] [ЖАНР: ЗАКАЗ] [РИСК: ПОДБОР]

Три слова. Вместо 50 строк инструкций. LLM сама понимает: «тема знакомая, это заказ, в подборе аналогов были ошибки — будь аккуратнее».

Как Контур «оценивает» запрос


Знакомость (familiarity) — асинхронный ChromaDB

Первый вопрос: а встречал ли ассистент такую тему раньше? Для этого нужен поиск по МНЕМОС — но он занимает 200-500 мс. Блокировать чат нельзя.

Решение — кэш + фон:

class FamiliarityCache:

TTL = 300 # 5 минут

async def get(self, userid, threadid):
key = (userid, threadid)
if key in self.cache and свежий:
return self.cache[key] # 0ms — хит
entry = {"familiarity": 0.5, "genre": "", "timestamp": now}
self.cache[key] = entry
asyncio.createtask(self.asyncfill(userid, thread_id, key)) # фон!
return entry

Первый запрос в треде получает familiarity=0.5 (дефолт) и запускает поиск в фоне. Со второго сообщения — точное значение из кэша, 0 миллисекунд. Память на 100 пользователей — ~300 KB. Никакой нагрузки.


Глубина диалога (complexity) — из STM

Анкоры STM — это «save points» диалога: каждая смена темы создаёт якорь. Контур смотрит на их количество:

цифры "1800x400x500"        → +0.3 (возможный Matematicus)

длина сообщения > 50 слов → +0.3 (нужен глубокий ответ)
> 3 анкоров в STM → +0.2 (долгий диалог, тема разрослась)

Много якорей = диалог долгий и ветвистый = LLM должна глубже копать. Мало = короткая переписка = ответ покороче.


Критичность — keyword-based

Иногда важнее не «знакомая ли тема», а «горит ли у пользователя»:

CRITICAL_WORDS = {

"срочно", "горим", "суд", "претензия", "жалоба",
"налоговая", "санкции", "блокировка", "штраф", "иск"
}

«Срочно нужен шкаф к пятнице» → criticality растёт. Плюс: аномально короткое сообщение (стресс) + негативный streak от Чутья → LLM становится внимательнее.


Метод — семантическая классификация (v4)

Самое важное обновление. Раньше жанр определялся keyword'ами — и это давало ложные срабатывания:

«какое сечение кабеля» → детект «заказ» ❌ (это же про кабель, а не мебель!)

Теперь метод классифицирует LLM — одна лёгкая классификация, ~100 токенов:

METHODVALUES = {"анализ", "бизнес", "сео", "расчёт", "заказ",

"поиск", "документы", "секретарь", "диалог"}

async def classifymethod(message: str) -> str:
# Fast-path: короткие без цифр → диалог (0мс)
if len(message.split()) <= 2 and not re.search(r'\d', message):
return "диалог"
# LLM-классификация (лёгкая модель, без хардкода)
...

Метод управляет загрузкой промптов:

МетодМодульЧто грузится
анализLogicusправила method=анализ
бизнесбизнес-промптыmethod=бизнес
сеоapp/seomethod=сео
расчётMatematicusmethod=расчёт
заказPrCoordinatorотдельный процессор
поискDeep Searchmethod=поиск
документыдокументыmethod=документы
секретарькалендарь/заметкиmethod=секретарь
диалогтолько base



Эффект: для простого «привет» грузится 3892 символа промпта вместо 11142. −65%. LLM перестала путаться — она получает правила ровно той задачи, которую решает.

Самообучение: Контур учится на оценках Sufler

У Контура есть UserModel — память о пользователе, которая обновляется после каждого ответа (по оценке Sufler, без дополнительных LLM-вызовов):

class UserModel:

trust: float = 0.5
frequent_topics: list = []
frustration_topics: list = []
avg_score: float = 0.0

def update(self, score, topic):
self.trust = self.trust 0.9 + (score / 5.0) 0.1
if score <= 2:
self.frustration_topics.append(topic) # зона боли
if score >= 4:
self.frequent_topics.append(topic) # сильные темы

Доверие влияет на уверенность:

if trust < 0.3:

sv.confidence = "LOW" # пользователь часто недоволен → осторожнее
elif trust > 0.7:
sv.confidence = "HIGH" # доволен → можно смелее

Модель хранится в general_settings, переживает рестарт, на 100 юзеров — ~50 KB памяти.


Как это выглядит в бою

Пример 1: заказ мебели (STM помнит тему)

STM: якорь «заказ мебели» + цифры 1800x400x500

Контур: familiarity=0.8 (из МНЕМОС), complexity=0.7 (анкоры STM), method="заказ"

Пользователь: «Шкаф 1800x400x500 нужен срочно к пятнице, подбери аналог»

Без контура: LLM объясняет про шкафы, теряет размеры
С контуром: [УВЕР: HIGH] [ЖАНР: ЗАКАЗ] [РИСК: ПОДБОР]
LLM: «Вижу активный заказ на шкафы. 1800×400×500 —
аналог Бриз Б-2 (совпадение 87%). Срок до пятницы принят.
Добавить в заказ?»

Пример 2: расчёт с НДС (МНЕМОС помнит ошибку)

STM: якорь «НДС, спецификация»

МНЕМОС L3: в теме НДС была ошибка (Sufler fail)

Пользователь: «Расчёт спецификации с НДС 20%»

Без контура: LLM считает с ошибкой в НДС
С контуром: [УВЕР: MEDIUM] [ЖАНР: РАСЧЁТ] [РИСК: НДС] [МООД: деловой]
LLM: «Давайте посчитаем. По опыту — с НДС бывают ошибки,
проверю 20%. Разрешите использовать Matematicus?»

Видите? Контур сам ничего не знает про шкафы и НДС. Он прочитал STM (тема, цифры), МНЕМОС (знакомая тема, были ошибки) — и превратил это в метку. Данные дали памяти, «озвучил» их контур.


Сравнение: Контур vs «просто хороший промпт»

ФичаОбычный промптИнтуитивный Контур
Размер инструкций50+ строк3-5 меток
Жанр запросаkeyword-детект (ошибки)LLM-классификация (метод)
Настроение 
нетЧутьё (mood)
Знакома ли теманетМНЕМОС async (familiarity)
Глубина диалоганетSTM (анкоры → complexity)
Где были ошибкинетМНЕМОС L3 → [РИСК: тема]
Доверие нетUserModel (самообучение)
Промпт для «привет»11 142 симв3 892 симв
Строк в main.py0

Три системы — одна картина

Чтобы не запутаться в трёх статьях:

СистемаРольЖивётПример
STMоперативная памятьодин диалогякорь «шкаф 1800×400, 2 шт»
МНЕМОСдолгосрочная памятьвечно«в НДС была ошибка»
КонтурСистема 1, читает обепереживает рестарт (UserModel)[ЖАНР: ЗАКАЗ] [РИСК: ПОДБОР]

Контур — это не память. Это голос памяти: он смотрит в STM и МНЕМОС и коротко говорит LLM, что там происходит.

«0 строк в main.py» — это принцип

Вся логика в app/contour.py (~600 строк) + константы вынесены в app/constants.py, чтобы не было циклических импортов (контур → prcoordinator → auth → контур). Главная боль архитектуры — изолированность: контур можно выключить, и ассистент просто станет «менее внимательным», но не сломается.


Сравнение с тем, что есть у других

Я не нашёл аналогов «pre-thinking слоя» как отдельного компонента у LLM-ассистентов. Ближайшие параллели:

  • Mem0 / Letta — память, но без оценки «настроения» и «жанра»
  • Function calling — определяет «какой инструмент», но не «в каком состоянии пользователь»
  • Prompt engineering — пытается решить всё инструкциями

Контур — по сути system prompt на стероидах: вместо «запомни 20 правил» LLM получает «вот оценка ситуации — реагируй». А данные для этой оценки берутся из STM и МНЕМОС, которые я описал в прошлых статьях.

Что дальше

В следующих статьях:

  • Замкнутый цикл самообучения AI-ассистента
  • Matematicus — 3-level matching для точных расчётов

Вопросы к читателям:

  1. Пробовали ли вы pre-processing слой перед LLM? Или решаете всё промптом?
  2. Как вы связываете «оценку ситуации» с памятью? Контур должен читать STM/МНЕМОС напрямую или получать готовые метрики?
  3. Стоит ли передавать criticality в промпт или это должно оставаться внутренним сигналом?

Если есть вопросы по конкретному компоненту — пишите в комментариях.

← АйТи, ИТ, IT - что это за термин? И кто такие айти
Нейросеть стала стандартом: но так ли хорошо ли эт →
Автору на кофе ☕🥐

Комментарии

Для комментирования авторизуйтесь на сайте или используйте ВКонтакте